Sfoglia il codice sorgente

Merge remote-tracking branch 'upstream/master'

kakwa 8 anni fa
parent
commit
ca728e5140
70 ha cambiato i file con 1401 aggiunte e 2084 eliminazioni
  1. 5 3
      CMakeLists.txt
  2. 6 3
      Qt/CivetWeb.pro
  3. 16 9
      README.md
  4. 19 2
      RELEASE_NOTES.md
  5. 14 3
      docs/UserManual.md
  6. 8 0
      examples/README.md
  7. 0 0
      examples/_obsolete/chat/Makefile
  8. 0 0
      examples/_obsolete/chat/chat.c
  9. 0 0
      examples/_obsolete/docroot/favicon.ico
  10. 0 0
      examples/_obsolete/docroot/index.html
  11. 0 0
      examples/_obsolete/docroot/jquery.js
  12. 0 0
      examples/_obsolete/docroot/login.html
  13. 0 0
      examples/_obsolete/docroot/logo.png
  14. 0 0
      examples/_obsolete/docroot/main.js
  15. 0 0
      examples/_obsolete/docroot/prime_numbers.lp
  16. 0 0
      examples/_obsolete/docroot/style.css
  17. 0 0
      examples/_obsolete/hello/Makefile
  18. 0 0
      examples/_obsolete/hello/hello.c
  19. 0 0
      examples/_obsolete/lua/lua_dll.c
  20. 0 0
      examples/_obsolete/post/Makefile
  21. 0 0
      examples/_obsolete/post/post.c
  22. 0 0
      examples/_obsolete/upload/Makefile
  23. 0 0
      examples/_obsolete/upload/upload.c
  24. 0 0
      examples/_obsolete/websocket/Makefile
  25. 0 0
      examples/_obsolete/websocket/WebSockCallbacks.c
  26. 0 0
      examples/_obsolete/websocket/WebSockCallbacks.h
  27. 0 0
      examples/_obsolete/websocket/websock.htm
  28. 0 0
      examples/_obsolete/websocket/websocket.c
  29. 0 0
      examples/_obsolete/websocket_client/Makefile
  30. 0 0
      examples/_obsolete/websocket_client/ssl/server.crt
  31. 0 0
      examples/_obsolete/websocket_client/ssl/server.csr
  32. 0 0
      examples/_obsolete/websocket_client/ssl/server.key
  33. 0 0
      examples/_obsolete/websocket_client/ssl/server.key.orig
  34. 0 0
      examples/_obsolete/websocket_client/ssl/server.pem
  35. 0 0
      examples/_obsolete/websocket_client/websocket_client.c
  36. 0 0
      examples/_obsolete/ws_server/Makefile
  37. 0 0
      examples/_obsolete/ws_server/docroot/index.html
  38. 0 0
      examples/_obsolete/ws_server/ws_server.c
  39. 1 1
      examples/embedded_c/embedded_c.c
  40. 22 5
      examples/embedded_cpp/embedded_cpp.cpp
  41. 1 1
      include/CivetServer.h
  42. 46 10
      include/civetweb.h
  43. 1 1
      resources/Makefile.in-duktape
  44. 1 1
      resources/Makefile.in-lua
  45. 1 1
      src/CivetServer.cpp
  46. 576 253
      src/civetweb.c
  47. 2 1
      src/civetweb_private_lua.h
  48. 1 1
      src/handle_form.inl
  49. 254 123
      src/main.c
  50. 1 1
      src/mod_duktape.inl
  51. 94 1
      src/mod_lua.inl
  52. 4 4
      src/third_party/LuaXML_lib.c
  53. 1 1
      src/third_party/civetweb_lua.h
  54. 65 24
      src/timer.inl
  55. 12 10
      test/CMakeLists.txt
  56. 1 1
      test/civetweb_check.h
  57. 1 1
      test/main.c
  58. 8 0
      test/page5.lua
  59. 18 18
      test/private.c
  60. 1 1
      test/private.h
  61. 1 1
      test/private_exe.c
  62. 1 1
      test/private_exe.h
  63. 1 1
      test/public_func.c
  64. 1 1
      test/public_func.h
  65. 32 3
      test/public_server.c
  66. 1 1
      test/public_server.h
  67. 1 1
      test/shared.c
  68. 1 1
      test/shared.h
  69. 182 45
      test/timertest.c
  70. 0 1549
      test/unit_test.c

+ 5 - 3
CMakeLists.txt

@@ -26,7 +26,7 @@ include(CMakeDependentOption)
 
 
 # Set up the project
 # Set up the project
 project (civetweb)
 project (civetweb)
-set(CIVETWEB_VERSION "1.7.0" CACHE STRING "The version of the civetweb library")
+set(CIVETWEB_VERSION "1.10.0" CACHE STRING "The version of the civetweb library")
 string(REGEX MATCH "([0-9]+)\\.([0-9]+)\\.([0-9]+)" CIVETWEB_VERSION_MATCH "${CIVETWEB_VERSION}")
 string(REGEX MATCH "([0-9]+)\\.([0-9]+)\\.([0-9]+)" CIVETWEB_VERSION_MATCH "${CIVETWEB_VERSION}")
 if ("${CIVETWEB_VERSION_MATCH}" STREQUAL "")
 if ("${CIVETWEB_VERSION_MATCH}" STREQUAL "")
   message(FATAL_ERROR "Must specify a semantic version: major.minor.patch")
   message(FATAL_ERROR "Must specify a semantic version: major.minor.patch")
@@ -416,14 +416,16 @@ add_subdirectory(src)
 include(CTest)
 include(CTest)
 if (BUILD_TESTING)
 if (BUILD_TESTING)
   # Check unit testing framework Version
   # Check unit testing framework Version
-  set(CIVETWEB_CHECK_VERSION 0.10.0 CACHE STRING
+  set(CIVETWEB_CHECK_VERSION 0.11.0 CACHE STRING
     "The version of Check unit testing framework to build and include statically")
     "The version of Check unit testing framework to build and include statically")
   set_property(CACHE CIVETWEB_CHECK_VERSION PROPERTY VALUE ${CIVETWEB_CHECK_VERSION})
   set_property(CACHE CIVETWEB_CHECK_VERSION PROPERTY VALUE ${CIVETWEB_CHECK_VERSION})
   message(STATUS "Check Unit Testing Framework Version - ${CIVETWEB_CHECK_VERSION}")
   message(STATUS "Check Unit Testing Framework Version - ${CIVETWEB_CHECK_VERSION}")
   mark_as_advanced(CIVETWEB_CHECK_VERSION)
   mark_as_advanced(CIVETWEB_CHECK_VERSION)
 
 
   # Check unit testing framework Verification Hash
   # Check unit testing framework Verification Hash
-  set(CIVETWEB_CHECK_MD5_HASH 67a34c40b5bc888737f4e5ae82e9939f CACHE STRING
+  # Hash for Check 0.10.0: 67a34c40b5bc888737f4e5ae82e9939f
+  # Hash for Check 0.11.0: 1b14ee307dca8e954a8219c34484d7c4
+  set(CIVETWEB_CHECK_MD5_HASH 1b14ee307dca8e954a8219c34484d7c4 CACHE STRING
     "The hash of Check unit testing framework archive to be downloaded")
     "The hash of Check unit testing framework archive to be downloaded")
   set_property(CACHE CIVETWEB_CHECK_MD5_HASH PROPERTY VALUE ${CIVETWEB_CHECK_MD5_HASH})
   set_property(CACHE CIVETWEB_CHECK_MD5_HASH PROPERTY VALUE ${CIVETWEB_CHECK_MD5_HASH})
   mark_as_advanced(CIVETWEB_CHECK_MD5_HASH)
   mark_as_advanced(CIVETWEB_CHECK_MD5_HASH)

+ 6 - 3
Qt/CivetWeb.pro

@@ -5,13 +5,15 @@ CONFIG -= qt
 
 
 SOURCES += \
 SOURCES += \
     ../src/md5.inl \
     ../src/md5.inl \
+    ../src/sha1.inl \
+    ../src/handle_form.inl \
     ../src/mod_lua.inl \
     ../src/mod_lua.inl \
     ../src/timer.inl \
     ../src/timer.inl \
     ../src/civetweb.c \
     ../src/civetweb.c \
     ../src/main.c
     ../src/main.c
 
 
-include(deployment.pri)
-qtcAddDeployment()
+#include(deployment.pri)
+#qtcAddDeployment()
 
 
 HEADERS += \
 HEADERS += \
     ../include/civetweb.h
     ../include/civetweb.h
@@ -19,7 +21,8 @@ HEADERS += \
 INCLUDEPATH +=  \
 INCLUDEPATH +=  \
     ../include/
     ../include/
 
 
-LIBS += -lws2_32 -lComdlg32
+LIBS += -lws2_32 -lComdlg32 -lUser32 -lShell32 -lAdvapi32
+
 
 
 DEFINES += USE_IPV6
 DEFINES += USE_IPV6
 DEFINES += USE_WEBSOCKET
 DEFINES += USE_WEBSOCKET

+ 16 - 9
README.md

@@ -16,7 +16,7 @@ Test coverage check ([coveralls](https://coveralls.io/github/civetweb/civetweb))
 
 
 [![Coverage Status](https://coveralls.io/repos/github/civetweb/civetweb/badge.svg?branch=master)](https://coveralls.io/github/civetweb/civetweb?branch=master)
 [![Coverage Status](https://coveralls.io/repos/github/civetweb/civetweb/badge.svg?branch=master)](https://coveralls.io/github/civetweb/civetweb?branch=master)
 
 
-Static source code analysis ([Coverity](https://scan.coverity.com/projects/5784)): 
+Static source code analysis ([Coverity](https://scan.coverity.com/projects/5784)):
 
 
 [![Coverity Scan Build Status](https://scan.coverity.com/projects/5784/badge.svg)](https://scan.coverity.com/projects/5784)
 [![Coverity Scan Build Status](https://scan.coverity.com/projects/5784/badge.svg)](https://scan.coverity.com/projects/5784)
 
 
@@ -49,6 +49,9 @@ Trouble tickets should be filed on GitHub
 Discussion/support group and announcements are at Google Groups
 Discussion/support group and announcements are at Google Groups
 [https://groups.google.com/d/forum/civetweb](https://groups.google.com/d/forum/civetweb)
 [https://groups.google.com/d/forum/civetweb](https://groups.google.com/d/forum/civetweb)
 
 
+Source releases can be found on GitHub
+[https://github.com/civetweb/civetweb/releases](https://github.com/civetweb/civetweb/releases)
+
 
 
 Quick start documentation
 Quick start documentation
 --------------------------
 --------------------------
@@ -58,6 +61,7 @@ Quick start documentation
 - [docs/Building.md](https://github.com/civetweb/civetweb/blob/master/docs/Building.md) - Building the Server (quick start guide)
 - [docs/Building.md](https://github.com/civetweb/civetweb/blob/master/docs/Building.md) - Building the Server (quick start guide)
 - [docs/Embedding.md](https://github.com/civetweb/civetweb/blob/master/docs/Embedding.md) - Embedding (how to add HTTP support to an existing application)
 - [docs/Embedding.md](https://github.com/civetweb/civetweb/blob/master/docs/Embedding.md) - Embedding (how to add HTTP support to an existing application)
 - [docs/OpenSSL.md](https://github.com/civetweb/civetweb/blob/master/docs/OpenSSL.md) - Adding HTTPS (SSL/TLS) support using OpenSSL.
 - [docs/OpenSSL.md](https://github.com/civetweb/civetweb/blob/master/docs/OpenSSL.md) - Adding HTTPS (SSL/TLS) support using OpenSSL.
+- [API documentation](https://github.com/civetweb/civetweb/tree/master/docs/api) - Additional documentation on the civetweb application programming interface ([civetweb.h](https://github.com/civetweb/civetweb/blob/master/include/civetweb.h)).
 - [RELEASE_NOTES.md](https://github.com/civetweb/civetweb/blob/master/RELEASE_NOTES.md) - Release Notes
 - [RELEASE_NOTES.md](https://github.com/civetweb/civetweb/blob/master/RELEASE_NOTES.md) - Release Notes
 - [LICENSE.md](https://github.com/civetweb/civetweb/blob/master/LICENSE.md) - Copyright License
 - [LICENSE.md](https://github.com/civetweb/civetweb/blob/master/LICENSE.md) - Copyright License
 
 
@@ -130,7 +134,8 @@ Contributions
 Contributions are welcome provided all contributions carry the MIT license.
 Contributions are welcome provided all contributions carry the MIT license.
 
 
 DO NOT APPLY fixes copied from Mongoose to this project to prevent GPL tainting.
 DO NOT APPLY fixes copied from Mongoose to this project to prevent GPL tainting.
-Since 2013 CivetWeb and Mongoose are developed independently. By now the code base differs, so patches cannot be safely transfered in either direction.
+Since 2013 CivetWeb and Mongoose are developed independently.
+By now the code base differs, so patches cannot be safely transfered in either direction.
 
 
 Some guidelines can be found in [docs/Contribution.md](https://github.com/civetweb/civetweb/blob/master/docs/Contribution.md).
 Some guidelines can be found in [docs/Contribution.md](https://github.com/civetweb/civetweb/blob/master/docs/Contribution.md).
 
 
@@ -142,14 +147,16 @@ Sergey Lyubka (Copyright (c) 2004-2013 Sergey Lyubka, MIT license).
 
 
 However, in August 16, 2013, the [license of Mongoose has been changed](https://groups.google.com/forum/#!topic/mongoose-users/aafbOnHonkI)
 However, in August 16, 2013, the [license of Mongoose has been changed](https://groups.google.com/forum/#!topic/mongoose-users/aafbOnHonkI)
 after writing and distributing the original code this project is based on.
 after writing and distributing the original code this project is based on.
-The license change used to be described on the Mongoose Wikipedia page as well, but it's getting deleted there regularly.
-
-CivetWeb has been forked from the last MIT version of Mongoose. 
-Since 2013, CivetWeb has seen many improvements from various authors 
-(Copyright (c) 2013-2016 the CivetWeb developers, MIT license).
+The license change and CivetWeb used to be mentioned on the Mongoose
+[Wikipedia](https://en.wikipedia.org/wiki/Mongoose_(web_server))
+page as well, but it's getting deleted (and added again) there every
+now and then.
+
+CivetWeb has been forked from the last MIT version of Mongoose.
+Since 2013, CivetWeb has seen many improvements from various authors
+(Copyright (c) 2013-2017 the CivetWeb developers, MIT license).
 A list of authors can be found in [CREDITS.md](https://github.com/civetweb/civetweb/blob/master/CREDITS.md).
 A list of authors can be found in [CREDITS.md](https://github.com/civetweb/civetweb/blob/master/CREDITS.md).
 
 
 Using the CivetWeb project ensures the MIT licenses terms are applied and
 Using the CivetWeb project ensures the MIT licenses terms are applied and
-GPL cannot be imposed on any of this code as long as it is sourced from
+GPL cannot be imposed on any of this code, as long as it is sourced from
 here. This code will remain free with the MIT license protection.
 here. This code will remain free with the MIT license protection.
-

+ 19 - 2
RELEASE_NOTES.md

@@ -1,10 +1,26 @@
-Release Notes v1.9 (work in progress)
+Release Notes v1.10 (work in progress)
 ===
 ===
-### Objectives: *Read client certificate information, bug fixes*
+### Objectives: *TO BE DEFINED*
 
 
 Changes
 Changes
 -------
 -------
 
 
+- Update version number
+
+
+Release Notes v1.9
+===
+### Objectives: *Read SSI client certificate information, improve windows usability, use non-blocking sockets, bug fixes*
+
+Changes
+-------
+
+- Add library init/exit functions (call is now optional, but will be required in V1.10)
+- Windows: Show system information from the tray icon
+- Windows: Bring overlaid windows to top from the tray icon
+- Add Lua background script, running independent from server state
+- Move obsolete examples into separated directory
+- Change name of CMake generated C++ library to civetweb-cpp
 - Add option to set linger timeout
 - Add option to set linger timeout
 - Update Duktape and Lua (third-party code)
 - Update Duktape and Lua (third-party code)
 - Add continuous integration tests
 - Add continuous integration tests
@@ -29,6 +45,7 @@ Changes
 - Read client certificate information
 - Read client certificate information
 - Do not tolerate URIs with invalid characters
 - Do not tolerate URIs with invalid characters
 - Fix mg_get_cookie to ignore substrings
 - Fix mg_get_cookie to ignore substrings
+- Fix memory leak in form handling
 - Fix bug in timer logic (for Lua Websockets)
 - Fix bug in timer logic (for Lua Websockets)
 - Updated version number
 - Updated version number
 
 

+ 14 - 3
docs/UserManual.md

@@ -235,8 +235,8 @@ are additional default index files, ordered before `index.cgi`.
 ### enable\_keep\_alive `no`
 ### enable\_keep\_alive `no`
 Enable connection keep alive, either `yes` or `no`.
 Enable connection keep alive, either `yes` or `no`.
 
 
-Experimental feature. Allows clients to reuse TCP connection for subsequent
-HTTP requests, which improves performance.
+Allows clients to reuse TCP connection for subsequent HTTP requests, 
+which improves performance.
 For this to work when using request handlers it is important to add the
 For this to work when using request handlers it is important to add the
 correct Content-Length HTTP header for each request. If this is forgotten the
 correct Content-Length HTTP header for each request. If this is forgotten the
 client will time out.
 client will time out.
@@ -393,7 +393,6 @@ Note: For consistency with other timeouts, the value is configured in
 milliseconds. However, the TCP socket layer usually only offers a timeout in 
 milliseconds. However, the TCP socket layer usually only offers a timeout in 
 seconds, so the value should be an integer multiple of 1000.
 seconds, so the value should be an integer multiple of 1000.
 
 
-
 ### lua\_preload\_file
 ### lua\_preload\_file
 This configuration option can be used to specify a Lua script file, which
 This configuration option can be used to specify a Lua script file, which
 is executed before the actual web page script (Lua script, Lua server page
 is executed before the actual web page script (Lua script, Lua server page
@@ -415,6 +414,18 @@ directly to the client. Lua script parts are delimited from the standard
 content by including them between <? and ?> tags.
 content by including them between <? and ?> tags.
 An example can be found in the test directory.
 An example can be found in the test directory.
 
 
+### lua\_background\_script
+Experimental feature, and subject to change.
+Run a Lua script in the background, independent from any connection.
+The script is started before network access to the server is available.
+It can be used to prepare the document root (e.g., update files, compress
+files, ...), check for external resources, remove old log files, etc.
+
+The Lua state remains open until the server is stopped.
+In the future, some callback functions will be available to notify the
+script on changes of the server state.
+
+
 ### websocket\_root
 ### websocket\_root
 In case civetweb is built with Lua and websocket support, Lua scripts may
 In case civetweb is built with Lua and websocket support, Lua scripts may
 be used for websockets as well. Since websockets use a different URL scheme
 be used for websockets as well. Since websockets use a different URL scheme

+ 8 - 0
examples/README.md

@@ -0,0 +1,8 @@
+
+Examples
+=====
+
+These examples show how to embed civetweb into a C ([embedded_c](https://github.com/civetweb/civetweb/tree/master/examples/embedded_c)) or a C++ ([embedded_cpp](https://github.com/civetweb/civetweb/tree/master/examples/embedded_cpp)) application.
+The C++ wrapper only offers a subset of the full C API, thus the C example is more complete than the C++ example.
+
+Some no longer maintained examples can be found in the ["obsolete"](https://github.com/civetweb/civetweb/tree/master/examples/_obsolete) folder. It is not guaranteed that they work in the current version - they are kept for reference, but might be removed in the future.

+ 0 - 0
examples/chat/Makefile → examples/_obsolete/chat/Makefile


+ 0 - 0
examples/chat/chat.c → examples/_obsolete/chat/chat.c


+ 0 - 0
examples/docroot/favicon.ico → examples/_obsolete/docroot/favicon.ico


+ 0 - 0
examples/docroot/index.html → examples/_obsolete/docroot/index.html


+ 0 - 0
examples/docroot/jquery.js → examples/_obsolete/docroot/jquery.js


+ 0 - 0
examples/docroot/login.html → examples/_obsolete/docroot/login.html


+ 0 - 0
examples/docroot/logo.png → examples/_obsolete/docroot/logo.png


+ 0 - 0
examples/docroot/main.js → examples/_obsolete/docroot/main.js


+ 0 - 0
examples/docroot/prime_numbers.lp → examples/_obsolete/docroot/prime_numbers.lp


+ 0 - 0
examples/docroot/style.css → examples/_obsolete/docroot/style.css


+ 0 - 0
examples/hello/Makefile → examples/_obsolete/hello/Makefile


+ 0 - 0
examples/hello/hello.c → examples/_obsolete/hello/hello.c


+ 0 - 0
examples/lua/lua_dll.c → examples/_obsolete/lua/lua_dll.c


+ 0 - 0
examples/post/Makefile → examples/_obsolete/post/Makefile


+ 0 - 0
examples/post/post.c → examples/_obsolete/post/post.c


+ 0 - 0
examples/upload/Makefile → examples/_obsolete/upload/Makefile


+ 0 - 0
examples/upload/upload.c → examples/_obsolete/upload/upload.c


+ 0 - 0
examples/websocket/Makefile → examples/_obsolete/websocket/Makefile


+ 0 - 0
examples/websocket/WebSockCallbacks.c → examples/_obsolete/websocket/WebSockCallbacks.c


+ 0 - 0
examples/websocket/WebSockCallbacks.h → examples/_obsolete/websocket/WebSockCallbacks.h


+ 0 - 0
examples/websocket/websock.htm → examples/_obsolete/websocket/websock.htm


+ 0 - 0
examples/websocket/websocket.c → examples/_obsolete/websocket/websocket.c


+ 0 - 0
examples/websocket_client/Makefile → examples/_obsolete/websocket_client/Makefile


+ 0 - 0
examples/websocket_client/ssl/server.crt → examples/_obsolete/websocket_client/ssl/server.crt


+ 0 - 0
examples/websocket_client/ssl/server.csr → examples/_obsolete/websocket_client/ssl/server.csr


+ 0 - 0
examples/websocket_client/ssl/server.key → examples/_obsolete/websocket_client/ssl/server.key


+ 0 - 0
examples/websocket_client/ssl/server.key.orig → examples/_obsolete/websocket_client/ssl/server.key.orig


+ 0 - 0
examples/websocket_client/ssl/server.pem → examples/_obsolete/websocket_client/ssl/server.pem


+ 0 - 0
examples/websocket_client/websocket_client.c → examples/_obsolete/websocket_client/websocket_client.c


+ 0 - 0
examples/ws_server/Makefile → examples/_obsolete/ws_server/Makefile


+ 0 - 0
examples/ws_server/docroot/index.html → examples/_obsolete/ws_server/docroot/index.html


+ 0 - 0
examples/ws_server/ws_server.c → examples/_obsolete/ws_server/ws_server.c


+ 1 - 1
examples/embedded_c/embedded_c.c

@@ -1,5 +1,5 @@
 /*
 /*
-* Copyright (c) 2013-2016 the CivetWeb developers
+* Copyright (c) 2013-2017 the CivetWeb developers
 * Copyright (c) 2013 No Face Press, LLC
 * Copyright (c) 2013 No Face Press, LLC
 * License http://opensource.org/licenses/mit-license.php MIT License
 * License http://opensource.org/licenses/mit-license.php MIT License
 */
 */

+ 22 - 5
examples/embedded_cpp/embedded_cpp.cpp

@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2016 the Civetweb developers
+/* Copyright (c) 2013-2017 the Civetweb developers
  * Copyright (c) 2013 No Face Press, LLC
  * Copyright (c) 2013 No Face Press, LLC
  * License http://opensource.org/licenses/mit-license.php MIT License
  * License http://opensource.org/licenses/mit-license.php MIT License
  */
  */
@@ -204,12 +204,29 @@ class FooHandler : public CivetHandler
 
 
 #ifdef _WIN32
 #ifdef _WIN32
         _snprintf(buf, sizeof(buf), "D:\\somewhere\\%s\\%s", req_info->remote_user, req_info->local_uri);
         _snprintf(buf, sizeof(buf), "D:\\somewhere\\%s\\%s", req_info->remote_user, req_info->local_uri);
-        buf[sizeof(buf)-1] = 0; /* TODO: check overflow */
-        f = fopen_recursive(buf, "wb");
+        buf[sizeof(buf)-1] = 0;
+        if (strlen(buf)>255) {
+            /* Windows will not work with path > 260 (MAX_PATH), unless we use
+             * the unicode API. However, this is just an example code: A real
+             * code will probably never store anything to D:\\somewhere and
+             * must be adapted to the specific needs anyhow. */
+            fail = 1;
+            f = NULL;
+        } else {
+            f = fopen_recursive(buf, "wb");
+        }
 #else
 #else
         snprintf(buf, sizeof(buf), "~/somewhere/%s/%s", req_info->remote_user, req_info->local_uri);
         snprintf(buf, sizeof(buf), "~/somewhere/%s/%s", req_info->remote_user, req_info->local_uri);
-        buf[sizeof(buf)-1] = 0; /* TODO: check overflow */
-        f = fopen_recursive(buf, "w");
+        buf[sizeof(buf)-1] = 0;
+        if (strlen(buf)>1020) {
+            /* The string is too long and probably truncated. Make sure an
+             * UTF-8 string is never truncated between the UTF-8 code bytes.
+             * This example code must be adapted to the specific needs. */
+            fail = 1;
+            f = NULL;
+        } else {
+            f = fopen_recursive(buf, "w");
+        }
 #endif
 #endif
 
 
         if (!f) {
         if (!f) {

+ 1 - 1
include/CivetServer.h

@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 the Civetweb developers
+/* Copyright (c) 2013-2017 the Civetweb developers
  * Copyright (c) 2013 No Face Press, LLC
  * Copyright (c) 2013 No Face Press, LLC
  *
  *
  * License http://opensource.org/licenses/mit-license.php MIT License
  * License http://opensource.org/licenses/mit-license.php MIT License

+ 46 - 10
include/civetweb.h

@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2016 the Civetweb developers
+/* Copyright (c) 2013-2017 the Civetweb developers
  * Copyright (c) 2004-2013 Sergey Lyubka
  * Copyright (c) 2004-2013 Sergey Lyubka
  *
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -23,7 +23,9 @@
 #ifndef CIVETWEB_HEADER_INCLUDED
 #ifndef CIVETWEB_HEADER_INCLUDED
 #define CIVETWEB_HEADER_INCLUDED
 #define CIVETWEB_HEADER_INCLUDED
 
 
-#define CIVETWEB_VERSION "1.9"
+#define CIVETWEB_VERSION "1.10"
+#define CIVETWEB_VERSION_MAJOR (1)
+#define CIVETWEB_VERSION_MINOR (10)
 
 
 #ifndef CIVETWEB_API
 #ifndef CIVETWEB_API
 #if defined(_WIN32)
 #if defined(_WIN32)
@@ -49,6 +51,25 @@ extern "C" {
 #endif /* __cplusplus */
 #endif /* __cplusplus */
 
 
 
 
+/* Initialize this library. This should be called once before any other
+ * function from this library. This function is not guaranteed to be
+ * thread safe.
+ * Parameters:
+ *   features: bit mask for features to be initialized.
+ * Return value:
+ *   initialized features
+ *   0: error
+ */
+CIVETWEB_API unsigned mg_init_library(unsigned features);
+
+
+/* Un-initialize this library.
+ * Return value:
+ *   0: error
+ */
+CIVETWEB_API unsigned mg_exit_library(void);
+
+
 struct mg_context;    /* Handle for the HTTP service itself */
 struct mg_context;    /* Handle for the HTTP service itself */
 struct mg_connection; /* Handle for the individual connection */
 struct mg_connection; /* Handle for the individual connection */
 
 
@@ -1049,9 +1070,16 @@ CIVETWEB_API int mg_get_response(struct mg_connection *conn,
                                  int timeout);
                                  int timeout);
 
 
 
 
-/* Check which features where set when civetweb has been compiled.
+/* Check which features where set when the civetweb library has been compiled.
+   The function explicitly addresses compile time defines used when building
+   the library - it does not mean, the feature has been initialized using a
+   mg_init_library call.
+   mg_check_feature can be called anytime, even before mg_init_library has
+   been called.
+
    Parameters:
    Parameters:
      feature: specifies which feature should be checked
      feature: specifies which feature should be checked
+       The value is a bit mask. The individual bits are defined as:
          1  serve files (NO_FILES not set)
          1  serve files (NO_FILES not set)
          2  support HTTPS (NO_SSL not set)
          2  support HTTPS (NO_SSL not set)
          4  support CGI (NO_CGI not set)
          4  support CGI (NO_CGI not set)
@@ -1060,22 +1088,30 @@ CIVETWEB_API int mg_get_response(struct mg_connection *conn,
         32  support Lua scripts and Lua server pages (USE_LUA is set)
         32  support Lua scripts and Lua server pages (USE_LUA is set)
         64  support server side JavaScript (USE_DUKTAPE is set)
         64  support server side JavaScript (USE_DUKTAPE is set)
        128  support caching (NO_CACHING not set)
        128  support caching (NO_CACHING not set)
-       The result is undefined for all other feature values.
+       The result is undefined, if bits are set that do not represent a
+       defined feature (currently: feature >= 256).
+       The result is undefined, if no bit is set (feature == 0).
 
 
    Return:
    Return:
-     If feature is available > 0
-     If feature is not available = 0
+     If feature is available, the corresponding bit is set
+     If feature is not available, the bit is 0
 */
 */
 CIVETWEB_API unsigned mg_check_feature(unsigned feature);
 CIVETWEB_API unsigned mg_check_feature(unsigned feature);
 
 
 
 
-/* Check which features where set when civetweb has been compiled.
+/* Get information on the system. Useful, if in support requests.
    Parameters:
    Parameters:
-     To be defined - 0, 0 prints to stdout.
+     buffer: Store system information as string here.
+     buflen: Length of buffer (including a byte required for a terminating 0).
    Return:
    Return:
-     To be defined.
+     Available size of system information, exluding a terminating 0.
+     The information is complete, if the return value is smaller than buflen.
+   Note:
+     It is possible to determine the required buflen, by first calling this
+     function with  buffer = NULL and buflen = NULL. The required buflen is
+     one byte more than the returned value.
 */
 */
-CIVETWEB_API int mg_print_system_info(int prm1, char *prm2);
+CIVETWEB_API int mg_get_system_info(char *buffer, int buflen);
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
 }
 }

+ 1 - 1
resources/Makefile.in-duktape

@@ -1,5 +1,5 @@
 #
 #
-# Copyright (c) 2015-2016 the Civetweb developers
+# Copyright (c) 2015-2017 the Civetweb developers
 #
 #
 # License http://opensource.org/licenses/mit-license.php MIT License
 # License http://opensource.org/licenses/mit-license.php MIT License
 #
 #

+ 1 - 1
resources/Makefile.in-lua

@@ -1,6 +1,6 @@
 #
 #
 # Copyright (c) 2013 No Face Press, LLC
 # Copyright (c) 2013 No Face Press, LLC
-# Copyright (c) 2014-2016 the Civetweb developers
+# Copyright (c) 2014-2017 the Civetweb developers
 #
 #
 # License http://opensource.org/licenses/mit-license.php MIT License
 # License http://opensource.org/licenses/mit-license.php MIT License
 #
 #

+ 1 - 1
src/CivetServer.cpp

@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 the Civetweb developers
+/* Copyright (c) 2013-2017 the Civetweb developers
  * Copyright (c) 2013 No Face Press, LLC
  * Copyright (c) 2013 No Face Press, LLC
  *
  *
  * License http://opensource.org/licenses/mit-license.php MIT License
  * License http://opensource.org/licenses/mit-license.php MIT License

+ 576 - 253
src/civetweb.c

@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2016 the Civetweb developers
+/* Copyright (c) 2013-2017 the Civetweb developers
  * Copyright (c) 2004-2013 Sergey Lyubka
  * Copyright (c) 2004-2013 Sergey Lyubka
  *
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -52,7 +52,7 @@
 #endif
 #endif
 #endif
 #endif
 
 
-#if defined(USE_LUA) && defined(USE_WEBSOCKET)
+#if defined(USE_LUA)
 #define USE_TIMERS
 #define USE_TIMERS
 #endif
 #endif
 
 
@@ -737,8 +737,8 @@ gmtime(const time_t *ptime)
 static size_t
 static size_t
 strftime(char *dst, size_t dst_size, const char *fmt, const struct tm *tm)
 strftime(char *dst, size_t dst_size, const char *fmt, const struct tm *tm)
 {
 {
-	/* TODO */ //(void)mg_snprintf(NULL, dst, dst_size, "implement strftime()
-	// for WinCE");
+	/* TODO: (void)mg_snprintf(NULL, dst, dst_size, "implement strftime()
+	 * for WinCE"); */
 	return 0;
 	return 0;
 }
 }
 
 
@@ -793,9 +793,10 @@ stat(const char *name, struct stat *st)
 #define access(x, a) 1 /* not required anyway */
 #define access(x, a) 1 /* not required anyway */
 
 
 /* WinCE-TODO: define stat, remove, rename, _rmdir, _lseeki64 */
 /* WinCE-TODO: define stat, remove, rename, _rmdir, _lseeki64 */
-#define EEXIST 1 /* TODO: See Windows error codes */
-#define EACCES 2 /* TODO: See Windows error codes */
-#define ENOENT 3 /* TODO: See Windows Error codes */
+/* Values from errno.h in Windows SDK (Visual Studio). */
+#define EEXIST 17
+#define EACCES 13
+#define ENOENT 2
 
 
 #if defined(__MINGW32__)
 #if defined(__MINGW32__)
 /* Enable unused function warning again */
 /* Enable unused function warning again */
@@ -1657,8 +1658,10 @@ struct mg_file_in_memory {
 struct mg_file_access {
 struct mg_file_access {
 	/* File properties filled by mg_fopen: */
 	/* File properties filled by mg_fopen: */
 	FILE *fp;
 	FILE *fp;
-	/* TODO: struct mg_file_in_memory *mf; */
-	const char *membuf; /* TODO: remove */
+	/* TODO (low): Replace "membuf" implementation by a "file in memory"
+	 * support library. Use some struct mg_file_in_memory *mf; instead of
+	 * membuf char pointer. */
+	const char *membuf;
 };
 };
 
 
 struct mg_file {
 struct mg_file {
@@ -1761,6 +1764,9 @@ enum {
 #if defined(_WIN32)
 #if defined(_WIN32)
 	CASE_SENSITIVE_FILES,
 	CASE_SENSITIVE_FILES,
 #endif
 #endif
+#if defined(USE_LUA)
+	LUA_BACKGROUND_SCRIPT,
+#endif
 
 
 	NUM_OPTIONS
 	NUM_OPTIONS
 };
 };
@@ -1843,6 +1849,9 @@ static struct mg_option config_options[] = {
 #if defined(_WIN32)
 #if defined(_WIN32)
     {"case_sensitive", CONFIG_TYPE_BOOLEAN, "no"},
     {"case_sensitive", CONFIG_TYPE_BOOLEAN, "no"},
 #endif
 #endif
+#if defined(USE_LUA)
+    {"lua_background_script", CONFIG_TYPE_FILE, NULL},
+#endif
 
 
     {NULL, CONFIG_TYPE_UNKNOWN, NULL}};
     {NULL, CONFIG_TYPE_UNKNOWN, NULL}};
 
 
@@ -1913,8 +1922,10 @@ struct mg_context {
 
 
 	pthread_t masterthreadid; /* The master thread ID */
 	pthread_t masterthreadid; /* The master thread ID */
 	unsigned int
 	unsigned int
-	    cfg_worker_threads;     /* The number of configured worker threads. */
-	pthread_t *workerthreadids; /* The worker thread IDs */
+	    cfg_worker_threads;      /* The number of configured worker threads. */
+	pthread_t *worker_threadids; /* The worker thread IDs */
+	struct mg_connection *worker_connections; /* The connection struct, pre-
+	                                           * allocated for each worker */
 
 
 	time_t start_time;        /* Server start time, used for authentication */
 	time_t start_time;        /* Server start time, used for authentication */
 	uint64_t auth_nonce_mask; /* Mask for all nonce values */
 	uint64_t auth_nonce_mask; /* Mask for all nonce values */
@@ -1931,9 +1942,13 @@ struct mg_context {
 	struct mg_shared_lua_websocket_list *shared_lua_websockets;
 	struct mg_shared_lua_websocket_list *shared_lua_websockets;
 #endif
 #endif
 
 
-#ifdef USE_TIMERS
+#if defined(USE_TIMERS)
 	struct ttimers *timers;
 	struct ttimers *timers;
 #endif
 #endif
+
+#if defined(USE_LUA)
+	void *lua_background_state;
+#endif
 };
 };
 
 
 
 
@@ -4663,7 +4678,18 @@ push(struct mg_context *ctx,
 		} else {
 		} else {
 			n = (int)send(sock, buf, (len_t)len, MSG_NOSIGNAL);
 			n = (int)send(sock, buf, (len_t)len, MSG_NOSIGNAL);
 			err = (n < 0) ? ERRNO : 0;
 			err = (n < 0) ? ERRNO : 0;
-			if (n <= 0) {
+#ifdef _WIN32
+			if (err == WSAEWOULDBLOCK) {
+				err = 0;
+				n = 0;
+			}
+#else
+			if (err == EWOULDBLOCK) {
+				err = 0;
+				n = 0;
+			}
+#endif
+			if (n < 0) {
 				/* shutdown of the socket at client side */
 				/* shutdown of the socket at client side */
 				return -1;
 				return -1;
 			}
 			}
@@ -4689,8 +4715,11 @@ push(struct mg_context *ctx,
 			/* socket error - check errno */
 			/* socket error - check errno */
 			DEBUG_TRACE("send() failed, error %d", err);
 			DEBUG_TRACE("send() failed, error %d", err);
 
 
-			/* TODO: error handling depending on the error code.
+			/* TODO (mid): error handling depending on the error code.
 			 * These codes are different between Windows and Linux.
 			 * These codes are different between Windows and Linux.
+			 * Currently there is no problem with failing send calls,
+			 * if there is a reproducible situation, it should be
+			 * investigated in detail.
 			 */
 			 */
 			return -1;
 			return -1;
 		}
 		}
@@ -4862,11 +4891,11 @@ pull(FILE *fp, struct mg_connection *conn, char *buf, int len, double timeout)
 /* socket error - check errno */
 /* socket error - check errno */
 #ifdef _WIN32
 #ifdef _WIN32
 		if (err == WSAEWOULDBLOCK) {
 		if (err == WSAEWOULDBLOCK) {
-			/* TODO: check if this is still required */
+			/* TODO (low): check if this is still required */
 			/* standard case if called from close_socket_gracefully */
 			/* standard case if called from close_socket_gracefully */
 			return -1;
 			return -1;
 		} else if (err == WSAETIMEDOUT) {
 		} else if (err == WSAETIMEDOUT) {
-			/* TODO: check if this is still required */
+			/* TODO (low): check if this is still required */
 			/* timeout is handled by the while loop  */
 			/* timeout is handled by the while loop  */
 			return 0;
 			return 0;
 		} else if (err == WSAECONNABORTED) {
 		} else if (err == WSAECONNABORTED) {
@@ -4883,7 +4912,7 @@ pull(FILE *fp, struct mg_connection *conn, char *buf, int len, double timeout)
 		 * here. We have to wait for the timeout in both cases for now.
 		 * here. We have to wait for the timeout in both cases for now.
 		 */
 		 */
 		if (err == EAGAIN || err == EWOULDBLOCK || err == EINTR) {
 		if (err == EAGAIN || err == EWOULDBLOCK || err == EINTR) {
-			/* TODO: check if this is still required */
+			/* TODO (low): check if this is still required */
 			/* EAGAIN/EWOULDBLOCK:
 			/* EAGAIN/EWOULDBLOCK:
 			 * standard case if called from close_socket_gracefully
 			 * standard case if called from close_socket_gracefully
 			 * => should return -1 */
 			 * => should return -1 */
@@ -5595,7 +5624,7 @@ interpret_uri(struct mg_connection *conn,    /* in: request (must be valid) */
               int *is_put_or_delete_request  /* out: put/delete a file? */
               int *is_put_or_delete_request  /* out: put/delete a file? */
               )
               )
 {
 {
-/* TODO (high): Restructure this function */
+/* TODO (high / maintainability issue): Restructure this function */
 
 
 #if !defined(NO_FILES)
 #if !defined(NO_FILES)
 	const char *uri = conn->request_info.local_uri;
 	const char *uri = conn->request_info.local_uri;
@@ -6180,10 +6209,10 @@ open_auth_file(struct mg_connection *conn,
 #endif
 #endif
 			}
 			}
 			/* Important: using local struct mg_file to test path for
 			/* Important: using local struct mg_file to test path for
-			 * is_directory
-			 * flag. If filep is used, mg_stat() makes it appear as if auth file
-			 * was opened. TODO: mg_stat must not make anything appear to be
-			 * opened */
+			 * is_directory flag. If filep is used, mg_stat() makes it
+			 * appear as if auth file was opened.
+			 * TODO(mid): Check if this is still required after rewriting
+			 * mg_stat */
 		} else if (mg_stat(conn, path, &filep->stat)
 		} else if (mg_stat(conn, path, &filep->stat)
 		           && filep->stat.is_directory) {
 		           && filep->stat.is_directory) {
 			mg_snprintf(conn,
 			mg_snprintf(conn,
@@ -8840,7 +8869,9 @@ mkcol(struct mg_connection *conn, const char *path)
 	}
 	}
 
 
 	if (de.file.last_modified) {
 	if (de.file.last_modified) {
-		/* TODO (high): This check does not seem to make any sense ! */
+		/* TODO (mid): This check does not seem to make any sense ! */
+		/* TODO (mid): Add a webdav unit test first, before changing
+		 * anything here. */
 		send_http_error(
 		send_http_error(
 		    conn, 405, "Error: mkcol(%s): %s", path, strerror(ERRNO));
 		    conn, 405, "Error: mkcol(%s): %s", path, strerror(ERRNO));
 		return;
 		return;
@@ -8983,7 +9014,7 @@ put_file(struct mg_connection *conn, const char *path)
 	}
 	}
 
 
 	/* A file should be created or overwritten. */
 	/* A file should be created or overwritten. */
-	/* TODO: Test if write or write+read is required. */
+	/* Currently CivetWeb does not nead read+write access. */
 	if (!mg_fopen(conn, path, MG_FOPEN_MODE_WRITE, &file)
 	if (!mg_fopen(conn, path, MG_FOPEN_MODE_WRITE, &file)
 	    || file.access.fp == NULL) {
 	    || file.access.fp == NULL) {
 		(void)mg_fclose(&file.access);
 		(void)mg_fclose(&file.access);
@@ -9367,10 +9398,12 @@ send_options(struct mg_connection *conn)
 	conn->must_close = 1;
 	conn->must_close = 1;
 	gmt_time_string(date, sizeof(date), &curtime);
 	gmt_time_string(date, sizeof(date), &curtime);
 
 
+	/* We do not set a "Cache-Control" header here, but leave the default.
+	 * Since browsers do not send an OPTIONS request, we can not test the
+	 * effect anyway. */
 	mg_printf(conn,
 	mg_printf(conn,
 	          "HTTP/1.1 200 OK\r\n"
 	          "HTTP/1.1 200 OK\r\n"
 	          "Date: %s\r\n"
 	          "Date: %s\r\n"
-	          /* TODO: "Cache-Control" (?) */
 	          "Connection: %s\r\n"
 	          "Connection: %s\r\n"
 	          "Allow: GET, POST, HEAD, CONNECT, PUT, DELETE, OPTIONS, "
 	          "Allow: GET, POST, HEAD, CONNECT, PUT, DELETE, OPTIONS, "
 	          "PROPFIND, MKCOL\r\n"
 	          "PROPFIND, MKCOL\r\n"
@@ -9954,8 +9987,9 @@ handle_websocket_request(struct mg_connection *conn,
 				curSubProtocol = protocol;
 				curSubProtocol = protocol;
 				len = sep ? (unsigned long)(sep - protocol)
 				len = sep ? (unsigned long)(sep - protocol)
 				          : (unsigned long)strlen(protocol);
 				          : (unsigned long)strlen(protocol);
-				while (sep && isspace(*++sep))
-					; // ignore leading whitespaces
+				while (sep && isspace(*++sep)) {
+					; /* ignore leading whitespaces */
+				}
 				protocol = sep;
 				protocol = sep;
 
 
 
 
@@ -9991,8 +10025,9 @@ handle_websocket_request(struct mg_connection *conn,
 				 * and use it to select one protocol among those the client has
 				 * and use it to select one protocol among those the client has
 				 * offered.
 				 * offered.
 				 */
 				 */
-				while (isspace(*++sep))
-					; // ignore leading whitespaces
+				while (isspace(*++sep)) {
+					; /* ignore leading whitespaces */
+				}
 				conn->request_info.acceptedWebSocketSubprotocol = sep;
 				conn->request_info.acceptedWebSocketSubprotocol = sep;
 			}
 			}
 		}
 		}
@@ -10033,9 +10068,9 @@ handle_websocket_request(struct mg_connection *conn,
 	/* Step 4: Check if there is a responsible websocket handler. */
 	/* Step 4: Check if there is a responsible websocket handler. */
 	if (!is_callback_resource && !lua_websock) {
 	if (!is_callback_resource && !lua_websock) {
 		/* There is no callback, and Lua is not responsible either. */
 		/* There is no callback, and Lua is not responsible either. */
-		/* Reply with a 404 Not Found or with nothing at all?
-		 * TODO (mid): check the websocket standards, how to reply to
-		 * requests to invalid websocket addresses. */
+		/* Reply with a 404 Not Found. We are still at a standard
+		 * HTTP request here, before the websocket handshake, so
+		 * we can still send standard HTTP error replies. */
 		send_http_error(conn, 404, "%s", "Not found");
 		send_http_error(conn, 404, "%s", "Not found");
 		return;
 		return;
 	}
 	}
@@ -10963,11 +10998,15 @@ handle_request(struct mg_connection *conn)
 					              &is_put_or_delete_request);
 					              &is_put_or_delete_request);
 					callback_handler = NULL;
 					callback_handler = NULL;
 
 
-					/* TODO (very low): goto is deprecated but for the
-					 * moment,
-					 * a goto is simpler than some curious loop. */
-					/* The situation "callback does not handle the request"
-					 * needs to be reconsidered anyway. */
+					/* Here we are at a dead end:
+					 * According to URI matching, a callback should be
+					 * responsible for handling the request,
+					 * we called it, but the callback declared itself
+					 * not responsible.
+					 * We use a goto here, to get out of this dead end,
+					 * and continue with the default handling.
+					 * A goto here is simpler and better to understand
+					 * than some curious loop. */
 					goto no_callback_resource;
 					goto no_callback_resource;
 				}
 				}
 			} else {
 			} else {
@@ -12736,8 +12775,8 @@ mg_close_connection(struct mg_connection *conn)
 
 
 		/* join worker thread */
 		/* join worker thread */
 		for (i = 0; i < client_ctx->cfg_worker_threads; i++) {
 		for (i = 0; i < client_ctx->cfg_worker_threads; i++) {
-			if (client_ctx->workerthreadids[i] != 0) {
-				mg_join_thread(client_ctx->workerthreadids[i]);
+			if (client_ctx->worker_threadids[i] != 0) {
+				mg_join_thread(client_ctx->worker_threadids[i]);
 			}
 			}
 		}
 		}
 	}
 	}
@@ -12755,7 +12794,7 @@ mg_close_connection(struct mg_connection *conn)
 
 
 	if (client_ctx != NULL) {
 	if (client_ctx != NULL) {
 		/* free context */
 		/* free context */
-		mg_free(client_ctx->workerthreadids);
+		mg_free(client_ctx->worker_threadids);
 		mg_free(client_ctx);
 		mg_free(client_ctx);
 		(void)pthread_mutex_destroy(&conn->mutex);
 		(void)pthread_mutex_destroy(&conn->mutex);
 		mg_free(conn);
 		mg_free(conn);
@@ -13493,7 +13532,7 @@ mg_connect_websocket_client(const char *host,
 	newctx->user_data = user_data;
 	newctx->user_data = user_data;
 	newctx->context_type = 2;       /* ws/wss client context type */
 	newctx->context_type = 2;       /* ws/wss client context type */
 	newctx->cfg_worker_threads = 1; /* one worker thread will be created */
 	newctx->cfg_worker_threads = 1; /* one worker thread will be created */
-	newctx->workerthreadids =
+	newctx->worker_threadids =
 	    (pthread_t *)mg_calloc(newctx->cfg_worker_threads, sizeof(pthread_t));
 	    (pthread_t *)mg_calloc(newctx->cfg_worker_threads, sizeof(pthread_t));
 	conn->ctx = newctx;
 	conn->ctx = newctx;
 	thread_data = (struct websocket_client_thread_data *)
 	thread_data = (struct websocket_client_thread_data *)
@@ -13508,9 +13547,9 @@ mg_connect_websocket_client(const char *host,
 	 * called on the client connection */
 	 * called on the client connection */
 	if (mg_start_thread_with_id(websocket_client_thread,
 	if (mg_start_thread_with_id(websocket_client_thread,
 	                            (void *)thread_data,
 	                            (void *)thread_data,
-	                            newctx->workerthreadids) != 0) {
+	                            newctx->worker_threadids) != 0) {
 		mg_free((void *)thread_data);
 		mg_free((void *)thread_data);
-		mg_free((void *)newctx->workerthreadids);
+		mg_free((void *)newctx->worker_threadids);
 		mg_free((void *)newctx);
 		mg_free((void *)newctx);
 		mg_free((void *)conn);
 		mg_free((void *)conn);
 		conn = NULL;
 		conn = NULL;
@@ -13823,116 +13862,134 @@ worker_thread_run(struct worker_thread_args *thread_args)
 	tls.pthread_cond_helper_mutex = CreateEvent(NULL, FALSE, FALSE, NULL);
 	tls.pthread_cond_helper_mutex = CreateEvent(NULL, FALSE, FALSE, NULL);
 #endif
 #endif
 
 
+	/* Initialize thread local storage before calling any callback */
+	pthread_setspecific(sTlsKey, &tls);
+
 	if (ctx->callbacks.init_thread) {
 	if (ctx->callbacks.init_thread) {
 		/* call init_thread for a worker thread (type 1) */
 		/* call init_thread for a worker thread (type 1) */
 		ctx->callbacks.init_thread(ctx, 1);
 		ctx->callbacks.init_thread(ctx, 1);
 	}
 	}
 
 
-	conn =
-	    (struct mg_connection *)mg_calloc(1, sizeof(*conn) + MAX_REQUEST_SIZE);
-	if (conn == NULL) {
-		mg_cry(fc(ctx), "%s", "Cannot create new connection struct, OOM");
-	} else {
-		pthread_setspecific(sTlsKey, &tls);
-		conn->buf_size = MAX_REQUEST_SIZE;
-		conn->buf = (char *)(conn + 1);
-		conn->ctx = ctx;
-		conn->thread_index = thread_args->index;
-		conn->request_info.user_data = ctx->user_data;
-		/* Allocate a mutex for this connection to allow communication both
-		 * within the request handler and from elsewhere in the application
-		 */
-		(void)pthread_mutex_init(&conn->mutex, &pthread_mutex_attr);
+	/* Connection structure has been pre-allocated */
+	if (((int)thread_args->index < 0)
+	    || ((unsigned)thread_args->index
+	        >= (unsigned)ctx->cfg_worker_threads)) {
+		mg_cry(fc(ctx),
+		       "Internal error: Invalid worker index %i",
+		       (int)thread_args->index);
+		return NULL;
+	}
+	conn = ctx->worker_connections + thread_args->index;
 
 
-		/* Call consume_socket() even when ctx->stop_flag > 0, to let it
-		 * signal sq_empty condvar to wake up the master waiting in
-		 * produce_socket() */
-		while (consume_socket(ctx, &conn->client, conn->thread_index)) {
-			conn->conn_birth_time = time(NULL);
+	/* Request buffers are not pre-allocated. They are private to the
+	 * request and do not contain any state information that might be
+	 * of interest to anyone observing a server status.  */
+	conn->buf = (char *)mg_malloc(MAX_REQUEST_SIZE);
+	if (conn->buf == NULL) {
+		mg_cry(fc(ctx),
+		       "Out of memory: Cannot allocate buffer for worker %i",
+		       (int)thread_args->index);
+		return NULL;
+	}
+	conn->buf_size = MAX_REQUEST_SIZE;
+
+	conn->ctx = ctx;
+	conn->thread_index = thread_args->index;
+	conn->request_info.user_data = ctx->user_data;
+	/* Allocate a mutex for this connection to allow communication both
+	 * within the request handler and from elsewhere in the application
+	 */
+	(void)pthread_mutex_init(&conn->mutex, &pthread_mutex_attr);
+
+	/* Call consume_socket() even when ctx->stop_flag > 0, to let it
+	 * signal sq_empty condvar to wake up the master waiting in
+	 * produce_socket() */
+	while (consume_socket(ctx, &conn->client, conn->thread_index)) {
+		conn->conn_birth_time = time(NULL);
 
 
 /* Fill in IP, port info early so even if SSL setup below fails,
 /* Fill in IP, port info early so even if SSL setup below fails,
  * error handler would have the corresponding info.
  * error handler would have the corresponding info.
  * Thanks to Johannes Winkelmann for the patch.
  * Thanks to Johannes Winkelmann for the patch.
  */
  */
 #if defined(USE_IPV6)
 #if defined(USE_IPV6)
-			if (conn->client.rsa.sa.sa_family == AF_INET6) {
-				conn->request_info.remote_port =
-				    ntohs(conn->client.rsa.sin6.sin6_port);
-			} else
+		if (conn->client.rsa.sa.sa_family == AF_INET6) {
+			conn->request_info.remote_port =
+			    ntohs(conn->client.rsa.sin6.sin6_port);
+		} else
 #endif
 #endif
-			{
-				conn->request_info.remote_port =
-				    ntohs(conn->client.rsa.sin.sin_port);
-			}
+		{
+			conn->request_info.remote_port =
+			    ntohs(conn->client.rsa.sin.sin_port);
+		}
 
 
-			sockaddr_to_string(conn->request_info.remote_addr,
-			                   sizeof(conn->request_info.remote_addr),
-			                   &conn->client.rsa);
+		sockaddr_to_string(conn->request_info.remote_addr,
+		                   sizeof(conn->request_info.remote_addr),
+		                   &conn->client.rsa);
 
 
-			DEBUG_TRACE("Start processing connection from %s",
-			            conn->request_info.remote_addr);
+		DEBUG_TRACE("Start processing connection from %s",
+		            conn->request_info.remote_addr);
 
 
 #if defined(MG_LEGACY_INTERFACE)
 #if defined(MG_LEGACY_INTERFACE)
-			/* This legacy interface only works for the IPv4 case */
-			addr = ntohl(conn->client.rsa.sin.sin_addr.s_addr);
-			memcpy(&conn->request_info.remote_ip, &addr, 4);
+		/* This legacy interface only works for the IPv4 case */
+		addr = ntohl(conn->client.rsa.sin.sin_addr.s_addr);
+		memcpy(&conn->request_info.remote_ip, &addr, 4);
 #endif
 #endif
 
 
-			conn->request_info.is_ssl = conn->client.is_ssl;
+		conn->request_info.is_ssl = conn->client.is_ssl;
 
 
-			if (conn->client.is_ssl) {
+		if (conn->client.is_ssl) {
 #ifndef NO_SSL
 #ifndef NO_SSL
-				/* HTTPS connection */
-				if (sslize(conn,
-				           conn->ctx->ssl_ctx,
-				           SSL_accept,
-				           &(conn->ctx->stop_flag))) {
-					/* Get SSL client certificate information (if set) */
-					ssl_get_client_cert_info(conn);
-
-					/* process HTTPS connection */
-					process_new_connection(conn);
-
-					/* Free client certificate info */
-					if (conn->request_info.client_cert) {
-						mg_free(
-						    (void *)(conn->request_info.client_cert->subject));
-						mg_free(
-						    (void *)(conn->request_info.client_cert->issuer));
-						mg_free(
-						    (void *)(conn->request_info.client_cert->serial));
-						mg_free(
-						    (void *)(conn->request_info.client_cert->finger));
-						conn->request_info.client_cert->subject = 0;
-						conn->request_info.client_cert->issuer = 0;
-						conn->request_info.client_cert->serial = 0;
-						conn->request_info.client_cert->finger = 0;
-						mg_free(conn->request_info.client_cert);
-						conn->request_info.client_cert = 0;
-					}
-				}
-#endif
-			} else {
-				/* process HTTP connection */
+			/* HTTPS connection */
+			if (sslize(conn,
+			           conn->ctx->ssl_ctx,
+			           SSL_accept,
+			           &(conn->ctx->stop_flag))) {
+				/* Get SSL client certificate information (if set) */
+				ssl_get_client_cert_info(conn);
+
+				/* process HTTPS connection */
 				process_new_connection(conn);
 				process_new_connection(conn);
+
+				/* Free client certificate info */
+				if (conn->request_info.client_cert) {
+					mg_free((void *)(conn->request_info.client_cert->subject));
+					mg_free((void *)(conn->request_info.client_cert->issuer));
+					mg_free((void *)(conn->request_info.client_cert->serial));
+					mg_free((void *)(conn->request_info.client_cert->finger));
+					conn->request_info.client_cert->subject = 0;
+					conn->request_info.client_cert->issuer = 0;
+					conn->request_info.client_cert->serial = 0;
+					conn->request_info.client_cert->finger = 0;
+					mg_free(conn->request_info.client_cert);
+					conn->request_info.client_cert = 0;
+				}
 			}
 			}
+#endif
+		} else {
+			/* process HTTP connection */
+			process_new_connection(conn);
+		}
 
 
-			DEBUG_TRACE("Done processing connection from %s (%f sec)",
-			            conn->request_info.remote_addr,
-			            difftime(time(NULL), conn->conn_birth_time));
+		DEBUG_TRACE("Done processing connection from %s (%f sec)",
+		            conn->request_info.remote_addr,
+		            difftime(time(NULL), conn->conn_birth_time));
 
 
-			close_connection(conn);
+		close_connection(conn);
 
 
-			DEBUG_TRACE("%s", "Connection closed");
-		}
+		DEBUG_TRACE("%s", "Connection closed");
 	}
 	}
 
 
+
 	pthread_setspecific(sTlsKey, NULL);
 	pthread_setspecific(sTlsKey, NULL);
 #if defined(_WIN32) && !defined(__SYMBIAN32__)
 #if defined(_WIN32) && !defined(__SYMBIAN32__)
 	CloseHandle(tls.pthread_cond_helper_mutex);
 	CloseHandle(tls.pthread_cond_helper_mutex);
 #endif
 #endif
 	pthread_mutex_destroy(&conn->mutex);
 	pthread_mutex_destroy(&conn->mutex);
-	mg_free(conn);
+
+	/* Free the request buffer. */
+	conn->buf_size = 0;
+	mg_free(conn->buf);
+	conn->buf = NULL;
 
 
 	DEBUG_TRACE("%s", "exiting");
 	DEBUG_TRACE("%s", "exiting");
 	return NULL;
 	return NULL;
@@ -13969,7 +14026,6 @@ accept_new_connection(const struct socket *listener, struct mg_context *ctx)
 	char src_addr[IP_ADDR_STR_LEN];
 	char src_addr[IP_ADDR_STR_LEN];
 	socklen_t len = sizeof(so.rsa);
 	socklen_t len = sizeof(so.rsa);
 	int on = 1;
 	int on = 1;
-	int timeout;
 
 
 	if (!listener) {
 	if (!listener) {
 		return;
 		return;
@@ -14030,19 +14086,10 @@ accept_new_connection(const struct socket *listener, struct mg_context *ctx)
 			}
 			}
 		}
 		}
 
 
-		if (ctx && ctx->config[REQUEST_TIMEOUT]) {
-			timeout = atoi(ctx->config[REQUEST_TIMEOUT]);
-		} else {
-			timeout = -1;
-		}
+		/* We are using non-blocking sockets. Thus, the
+		 * set_sock_timeout(so.sock, timeout);
+		 * call is no longer required. */
 
 
-
-		/* TODO: if non blocking sockets are used, timeouts are implemented
-		 * differently */
-		// if (timeout > 0) {
-		//	set_sock_timeout(so.sock, timeout);
-		//}
-		(void)timeout;
 		set_blocking_mode(so.sock, 0);
 		set_blocking_mode(so.sock, 0);
 
 
 		produce_socket(ctx, &so);
 		produce_socket(ctx, &so);
@@ -14142,11 +14189,19 @@ master_thread_run(void *thread_func_param)
 	/* Join all worker threads to avoid leaking threads. */
 	/* Join all worker threads to avoid leaking threads. */
 	workerthreadcount = ctx->cfg_worker_threads;
 	workerthreadcount = ctx->cfg_worker_threads;
 	for (i = 0; i < workerthreadcount; i++) {
 	for (i = 0; i < workerthreadcount; i++) {
-		if (ctx->workerthreadids[i] != 0) {
-			mg_join_thread(ctx->workerthreadids[i]);
+		if (ctx->worker_threadids[i] != 0) {
+			mg_join_thread(ctx->worker_threadids[i]);
 		}
 		}
 	}
 	}
 
 
+#if defined(USE_LUA)
+	/* Free Lua state of lua background task */
+	if (ctx->lua_background_state) {
+		lua_close((lua_State *)ctx->lua_background_state);
+		ctx->lua_background_state = 0;
+	}
+#endif
+
 #if !defined(NO_SSL)
 #if !defined(NO_SSL)
 	if (ctx->ssl_ctx != NULL) {
 	if (ctx->ssl_ctx != NULL) {
 		uninitialize_ssl(ctx);
 		uninitialize_ssl(ctx);
@@ -14245,8 +14300,13 @@ free_context(struct mg_context *ctx)
 #endif /* !NO_SSL */
 #endif /* !NO_SSL */
 
 
 	/* Deallocate worker thread ID array */
 	/* Deallocate worker thread ID array */
-	if (ctx->workerthreadids != NULL) {
-		mg_free(ctx->workerthreadids);
+	if (ctx->worker_threadids != NULL) {
+		mg_free(ctx->worker_threadids);
+	}
+
+	/* Deallocate worker thread ID array */
+	if (ctx->worker_connections != NULL) {
+		mg_free(ctx->worker_connections);
 	}
 	}
 
 
 	/* Deallocate the tls variable */
 	/* Deallocate the tls variable */
@@ -14508,6 +14568,24 @@ mg_start(const struct mg_callbacks *callbacks,
 
 
 	get_system_name(&ctx->systemName);
 	get_system_name(&ctx->systemName);
 
 
+#if defined(USE_LUA)
+	/* If a Lua background script has been configured, start it. */
+	if (ctx->config[LUA_BACKGROUND_SCRIPT] != NULL) {
+		char ebuf[256];
+		void *state = (void *)mg_prepare_lua_context_script(
+		    ctx->config[LUA_BACKGROUND_SCRIPT], ctx, ebuf, sizeof(ebuf));
+		if (!state) {
+			mg_cry(fc(ctx), "lua_background_script error: %s", ebuf);
+			free_context(ctx);
+			pthread_setspecific(sTlsKey, NULL);
+			return NULL;
+		}
+		ctx->lua_background_state = state;
+	} else {
+		ctx->lua_background_state = 0;
+	}
+#endif
+
 	/* NOTE(lsm): order is important here. SSL certificates must
 	/* NOTE(lsm): order is important here. SSL certificates must
 	 * be initialized before listening ports. UID must be set last. */
 	 * be initialized before listening ports. UID must be set last. */
 	if (!set_gpass_option(ctx) ||
 	if (!set_gpass_option(ctx) ||
@@ -14531,21 +14609,31 @@ mg_start(const struct mg_callbacks *callbacks,
 #endif /* !_WIN32 && !__SYMBIAN32__ */
 #endif /* !_WIN32 && !__SYMBIAN32__ */
 
 
 	ctx->cfg_worker_threads = ((unsigned int)(workerthreadcount));
 	ctx->cfg_worker_threads = ((unsigned int)(workerthreadcount));
-	ctx->workerthreadids =
+	ctx->worker_threadids =
 	    (pthread_t *)mg_calloc(ctx->cfg_worker_threads, sizeof(pthread_t));
 	    (pthread_t *)mg_calloc(ctx->cfg_worker_threads, sizeof(pthread_t));
-	if (ctx->workerthreadids == NULL) {
+	if (ctx->worker_threadids == NULL) {
 		mg_cry(fc(ctx), "Not enough memory for worker thread ID array");
 		mg_cry(fc(ctx), "Not enough memory for worker thread ID array");
 		free_context(ctx);
 		free_context(ctx);
 		pthread_setspecific(sTlsKey, NULL);
 		pthread_setspecific(sTlsKey, NULL);
 		return NULL;
 		return NULL;
 	}
 	}
+	ctx->worker_connections =
+	    (struct mg_connection *)mg_calloc(ctx->cfg_worker_threads,
+	                                      sizeof(struct mg_connection));
+	if (ctx->worker_connections == NULL) {
+		mg_cry(fc(ctx), "Not enough memory for worker thread connection array");
+		free_context(ctx);
+		pthread_setspecific(sTlsKey, NULL);
+		return NULL;
+	}
+
 
 
 #if defined(ALTERNATIVE_QUEUE)
 #if defined(ALTERNATIVE_QUEUE)
 	ctx->client_wait_events =
 	ctx->client_wait_events =
 	    mg_calloc(sizeof(ctx->client_wait_events[0]), ctx->cfg_worker_threads);
 	    mg_calloc(sizeof(ctx->client_wait_events[0]), ctx->cfg_worker_threads);
 	if (ctx->client_wait_events == NULL) {
 	if (ctx->client_wait_events == NULL) {
 		mg_cry(fc(ctx), "Not enough memory for worker event array");
 		mg_cry(fc(ctx), "Not enough memory for worker event array");
-		mg_free(ctx->workerthreadids);
+		mg_free(ctx->worker_threadids);
 		free_context(ctx);
 		free_context(ctx);
 		pthread_setspecific(sTlsKey, NULL);
 		pthread_setspecific(sTlsKey, NULL);
 		return NULL;
 		return NULL;
@@ -14556,7 +14644,7 @@ mg_start(const struct mg_callbacks *callbacks,
 	if (ctx->client_wait_events == NULL) {
 	if (ctx->client_wait_events == NULL) {
 		mg_cry(fc(ctx), "Not enough memory for worker socket array");
 		mg_cry(fc(ctx), "Not enough memory for worker socket array");
 		mg_free(ctx->client_socks);
 		mg_free(ctx->client_socks);
-		mg_free(ctx->workerthreadids);
+		mg_free(ctx->worker_threadids);
 		free_context(ctx);
 		free_context(ctx);
 		pthread_setspecific(sTlsKey, NULL);
 		pthread_setspecific(sTlsKey, NULL);
 		return NULL;
 		return NULL;
@@ -14566,7 +14654,15 @@ mg_start(const struct mg_callbacks *callbacks,
 		ctx->client_wait_events[i] = event_create();
 		ctx->client_wait_events[i] = event_create();
 		if (ctx->client_wait_events[i] == 0) {
 		if (ctx->client_wait_events[i] == 0) {
 			mg_cry(fc(ctx), "Error creating worker event %i", i);
 			mg_cry(fc(ctx), "Error creating worker event %i", i);
-			/* TODO: clean all and exit */
+			while (i > 0) {
+				i--;
+				event_destroy(ctx->client_wait_events[i]);
+			}
+			mg_free(ctx->client_socks);
+			mg_free(ctx->worker_threadids);
+			free_context(ctx);
+			pthread_setspecific(sTlsKey, NULL);
+			return NULL;
 		}
 		}
 	}
 	}
 #endif
 #endif
@@ -14603,7 +14699,7 @@ mg_start(const struct mg_callbacks *callbacks,
 		if ((wta == NULL)
 		if ((wta == NULL)
 		    || (mg_start_thread_with_id(worker_thread,
 		    || (mg_start_thread_with_id(worker_thread,
 		                                wta,
 		                                wta,
-		                                &ctx->workerthreadids[i]) != 0)) {
+		                                &ctx->worker_threadids[i]) != 0)) {
 
 
 			/* thread was not created */
 			/* thread was not created */
 			if (wta != NULL) {
 			if (wta != NULL) {
@@ -14687,158 +14783,385 @@ mg_check_feature(unsigned feature)
 }
 }
 
 
 
 
-/* Print system information.
- * TODO: define parameters. */
-int
-mg_print_system_info(int prm1, char *prm2)
+/* Get system information. It can be printed or stored by the caller.
+ * Return the size of available information. */
+static int
+mg_get_system_info_impl(char *buffer, int buflen)
 {
 {
-    //WARNING: these parameters are not being used
-	(void)prm1;
-	(void)prm2;
-	
-	const char *version = mg_version();
+	char block[256];
+	int system_info_length = 0;
+
+#if defined(_WIN32)
+	const char *eol = "\r\n";
+#else
+	const char *eol = "\n";
+#endif
+
+	/* Server version */
+	{
+		const char *version = mg_version();
+		mg_snprintf(NULL,
+		            NULL,
+		            block,
+		            sizeof(block),
+		            "Server Version: %s%s",
+		            version,
+		            eol);
+		system_info_length += (int)strlen(block);
+		if (system_info_length < buflen) {
+			strcat(buffer, block);
+		}
+	}
+
+	/* System info */
+	{
 #if defined(_WIN32)
 #if defined(_WIN32)
 #if !defined(__SYMBIAN32__)
 #if !defined(__SYMBIAN32__)
-	DWORD dwVersion = 0;
-	DWORD dwMajorVersion = 0;
-	DWORD dwMinorVersion = 0;
-	SYSTEM_INFO si;
+		DWORD dwVersion = 0;
+		DWORD dwMajorVersion = 0;
+		DWORD dwMinorVersion = 0;
+		SYSTEM_INFO si;
 
 
-	GetSystemInfo(&si);
+		GetSystemInfo(&si);
 
 
 #ifdef _MSC_VER
 #ifdef _MSC_VER
 #pragma warning(push)
 #pragma warning(push)
-// GetVersion was declared deprecated
+/* GetVersion was declared deprecated */
 #pragma warning(disable : 4996)
 #pragma warning(disable : 4996)
 #endif
 #endif
-	dwVersion = GetVersion();
+		dwVersion = GetVersion();
 #ifdef _MSC_VER
 #ifdef _MSC_VER
 #pragma warning(pop)
 #pragma warning(pop)
 #endif
 #endif
 
 
-	dwMajorVersion = (DWORD)(LOBYTE(LOWORD(dwVersion)));
-	dwMinorVersion = (DWORD)(HIBYTE(LOWORD(dwVersion)));
+		dwMajorVersion = (DWORD)(LOBYTE(LOWORD(dwVersion)));
+		dwMinorVersion = (DWORD)(HIBYTE(LOWORD(dwVersion)));
 
 
-	fprintf(stdout,
-	        "\nWindows %u.%u\n",
-	        (unsigned)dwMajorVersion,
-	        (unsigned)dwMinorVersion);
+		mg_snprintf(NULL,
+		            NULL,
+		            block,
+		            sizeof(block),
+		            "Windows %u.%u%s",
+		            (unsigned)dwMajorVersion,
+		            (unsigned)dwMinorVersion,
+		            eol);
+		system_info_length += (int)strlen(block);
+		if (system_info_length < buflen) {
+			strcat(buffer, block);
+		}
 
 
-	fprintf(stdout,
-	        "CPU: type %u, cores %u, mask %x\n",
-	        (unsigned)si.wProcessorArchitecture,
-	        (unsigned)si.dwNumberOfProcessors,
-	        (unsigned)si.dwActiveProcessorMask);
+		mg_snprintf(NULL,
+		            NULL,
+		            block,
+		            sizeof(block),
+		            "CPU: type %u, cores %u, mask %x%s",
+		            (unsigned)si.wProcessorArchitecture,
+		            (unsigned)si.dwNumberOfProcessors,
+		            (unsigned)si.dwActiveProcessorMask,
+		            eol);
+		system_info_length += (int)strlen(block);
+		if (system_info_length < buflen) {
+			strcat(buffer, block);
+		}
 
 
 #else
 #else
-	fprintf(stdout, "\n%s - Symbian\n");
+		mg_snprintf(NULL, NULL, block, sizeof(block), "%s - Symbian%s", eol);
+		system_info_length += (int)strlen(block);
+		if (system_info_length < buflen) {
+			strcat(buffer, block);
+		}
 #endif
 #endif
 #else
 #else
-	struct utsname name;
-	memset(&name, 0, sizeof(name));
-	uname(&name);
+		struct utsname name;
+		memset(&name, 0, sizeof(name));
+		uname(&name);
 
 
-	fprintf(stdout,
-	        "\n%s %s (%s) - %s\n",
-	        name.sysname,
-	        name.version,
-	        name.release,
-	        name.machine);
+		mg_snprintf(NULL,
+		            NULL,
+		            block,
+		            sizeof(block),
+		            "%s %s (%s) - %s%s",
+		            name.sysname,
+		            name.version,
+		            name.release,
+		            name.machine,
+		            eol);
+		system_info_length += (int)strlen(block);
+		if (system_info_length < buflen) {
+			strcat(buffer, block);
+		}
 #endif
 #endif
-
-	fprintf(stdout, "Features:");
-	if (mg_check_feature(1)) {
-		fprintf(stdout, " Files");
-	}
-	if (mg_check_feature(2)) {
-		fprintf(stdout, " HTTPS");
-	}
-	if (mg_check_feature(4)) {
-		fprintf(stdout, " CGI");
-	}
-	if (mg_check_feature(8)) {
-		fprintf(stdout, " IPv6");
-	}
-	if (mg_check_feature(16)) {
-		fprintf(stdout, " WebSockets");
 	}
 	}
-	if (mg_check_feature(32)) {
-		fprintf(stdout, " Lua");
-	}
-	fprintf(stdout, "\n");
+
+	/* Features */
+	{
+		mg_snprintf(NULL,
+		            NULL,
+		            block,
+		            sizeof(block),
+		            "Features: %X%s%s%s%s%s%s%s%s%s%s",
+		            mg_check_feature(0xFFFFFFFFu),
+		            eol,
+		            mg_check_feature(1) ? " Files" : "",
+		            mg_check_feature(2) ? " HTTPS" : "",
+		            mg_check_feature(4) ? " CGI" : "",
+		            mg_check_feature(8) ? " IPv6" : "",
+		            mg_check_feature(16) ? " WebSockets" : "",
+		            mg_check_feature(32) ? " Lua" : "",
+		            mg_check_feature(64) ? " JavaScript" : "",
+		            mg_check_feature(128) ? " Cache" : "",
+		            eol);
+		system_info_length += (int)strlen(block);
+		if (system_info_length < buflen) {
+			strcat(buffer, block);
+		}
 
 
 #ifdef USE_LUA
 #ifdef USE_LUA
-	fprintf(stdout,
-	        "Lua Version: %u (%s)\n",
-	        (unsigned)LUA_VERSION_NUM,
-	        LUA_RELEASE);
+		mg_snprintf(NULL,
+		            NULL,
+		            block,
+		            sizeof(block),
+		            "Lua Version: %u (%s)%s",
+		            (unsigned)LUA_VERSION_NUM,
+		            LUA_RELEASE,
+		            eol);
+		system_info_length += (int)strlen(block);
+		if (system_info_length < buflen) {
+			strcat(buffer, block);
+		}
 #endif
 #endif
+#if defined(USE_DUKTAPE)
+		mg_snprintf(NULL,
+		            NULL,
+		            block,
+		            sizeof(block),
+		            "JavaScript: Duktape %u.%u.%u%s",
+		            (unsigned)DUK_VERSION / 10000,
+		            ((unsigned)DUK_VERSION / 100) % 100,
+		            (unsigned)DUK_VERSION % 100,
+		            eol);
+		system_info_length += (int)strlen(block);
+		if (system_info_length < buflen) {
+			strcat(buffer, block);
+		}
+#endif
+	}
 
 
-	fprintf(stdout, "Version: %s\n", version);
+	/* Build date */
+	{
+		mg_snprintf(
+		    NULL, NULL, block, sizeof(block), "Build: %s%s", __DATE__, eol);
+		system_info_length += (int)strlen(block);
+		if (system_info_length < buflen) {
+			strcat(buffer, block);
+		}
+	}
 
 
-	fprintf(stdout, "Build: %s\n", __DATE__);
 
 
-/* http://sourceforge.net/p/predef/wiki/Compilers/ */
+	/* Compiler information */
+	/* http://sourceforge.net/p/predef/wiki/Compilers/ */
+	{
 #if defined(_MSC_VER)
 #if defined(_MSC_VER)
-	fprintf(stdout,
-	        "MSC: %u (%u)\n",
-	        (unsigned)_MSC_VER,
-	        (unsigned)_MSC_FULL_VER);
+		mg_snprintf(NULL,
+		            NULL,
+		            block,
+		            sizeof(block),
+		            "MSC: %u (%u)%s",
+		            (unsigned)_MSC_VER,
+		            (unsigned)_MSC_FULL_VER,
+		            eol);
+		system_info_length += (int)strlen(block);
+		if (system_info_length < buflen) {
+			strcat(buffer, block);
+		}
 #elif defined(__MINGW64__)
 #elif defined(__MINGW64__)
-	fprintf(stdout,
-	        "MinGW64: %u.%u\n",
-	        (unsigned)__MINGW64_VERSION_MAJOR,
-	        (unsigned)__MINGW64_VERSION_MINOR);
-	fprintf(stdout,
-	        "MinGW32: %u.%u\n",
-	        (unsigned)__MINGW32_MAJOR_VERSION,
-	        (unsigned)__MINGW32_MINOR_VERSION);
+		mg_snprintf(NULL,
+		            NULL,
+		            block,
+		            sizeof(block),
+		            "MinGW64: %u.%u%s",
+		            (unsigned)__MINGW64_VERSION_MAJOR,
+		            (unsigned)__MINGW64_VERSION_MINOR,
+		            eol);
+		system_info_length += (int)strlen(block);
+		if (system_info_length < buflen) {
+			strcat(buffer, block);
+		}
+		mg_snprintf(NULL,
+		            NULL,
+		            block,
+		            sizeof(block),
+		            "MinGW32: %u.%u%s",
+		            (unsigned)__MINGW32_MAJOR_VERSION,
+		            (unsigned)__MINGW32_MINOR_VERSION,
+		            eol);
+		system_info_length += (int)strlen(block);
+		if (system_info_length < buflen) {
+			strcat(buffer, block);
+		}
 #elif defined(__MINGW32__)
 #elif defined(__MINGW32__)
-	fprintf(stdout,
-	        "MinGW32: %u.%u\n",
-	        (unsigned)__MINGW32_MAJOR_VERSION,
-	        (unsigned)__MINGW32_MINOR_VERSION);
+		mg_snprintf(NULL,
+		            NULL,
+		            block,
+		            sizeof(block),
+		            "MinGW32: %u.%u%s",
+		            (unsigned)__MINGW32_MAJOR_VERSION,
+		            (unsigned)__MINGW32_MINOR_VERSION,
+		            eol);
+		system_info_length += (int)strlen(block);
+		if (system_info_length < buflen) {
+			strcat(buffer, block);
+		}
 #elif defined(__clang__)
 #elif defined(__clang__)
-	fprintf(stdout,
-	        "clang: %u.%u.%u (%s)\n",
-	        __clang_major__,
-	        __clang_minor__,
-	        __clang_patchlevel__,
-	        __clang_version__);
+		mg_snprintf(NULL,
+		            NULL,
+		            block,
+		            sizeof(block),
+		            "clang: %u.%u.%u (%s)%s",
+		            __clang_major__,
+		            __clang_minor__,
+		            __clang_patchlevel__,
+		            __clang_version__,
+		            eol);
+		system_info_length += (int)strlen(block);
+		if (system_info_length < buflen) {
+			strcat(buffer, block);
+		}
 #elif defined(__GNUC__)
 #elif defined(__GNUC__)
-	fprintf(stdout,
-	        "gcc: %u.%u.%u\n",
-	        (unsigned)__GNUC__,
-	        (unsigned)__GNUC_MINOR__,
-	        (unsigned)__GNUC_PATCHLEVEL__);
+		mg_snprintf(NULL,
+		            NULL,
+		            block,
+		            sizeof(block),
+		            "gcc: %u.%u.%u%s",
+		            (unsigned)__GNUC__,
+		            (unsigned)__GNUC_MINOR__,
+		            (unsigned)__GNUC_PATCHLEVEL__,
+		            eol);
+		system_info_length += (int)strlen(block);
+		if (system_info_length < buflen) {
+			strcat(buffer, block);
+		}
 #elif defined(__INTEL_COMPILER)
 #elif defined(__INTEL_COMPILER)
-	fprintf(stdout, "Intel C/C++: %u\n", (unsigned)__INTEL_COMPILER);
+		mg_snprintf(NULL,
+		            NULL,
+		            block,
+		            sizeof(block),
+		            "Intel C/C++: %u%s",
+		            (unsigned)__INTEL_COMPILER,
+		            eol);
+		system_info_length += (int)strlen(block);
+		if (system_info_length < buflen) {
+			strcat(buffer, block);
+		}
 #elif defined(__BORLANDC__)
 #elif defined(__BORLANDC__)
-	fprintf(stdout, "Borland C: 0x%x\n", (unsigned)__BORLANDC__);
+		mg_snprintf(NULL,
+		            NULL,
+		            block,
+		            sizeof(block),
+		            "Borland C: 0x%x%s",
+		            (unsigned)__BORLANDC__,
+		            eol);
+		system_info_length += (int)strlen(block);
+		if (system_info_length < buflen) {
+			strcat(buffer, block);
+		}
 #elif defined(__SUNPRO_C)
 #elif defined(__SUNPRO_C)
-	fprintf(stdout, "Solaris: 0x%x\n", (unsigned)__SUNPRO_C);
+		mg_snprintf(NULL,
+		            NULL,
+		            block,
+		            sizeof(block),
+		            "Solaris: 0x%x%s",
+		            (unsigned)__SUNPRO_C,
+		            eol);
+		system_info_length += (int)strlen(block);
+		if (system_info_length < buflen) {
+			strcat(buffer, block);
+		}
 #else
 #else
-	fprintf(stdout, "Other\n");
+		mg_snprintf(NULL, NULL, block, sizeof(block), "Other compiler%s", eol);
+		system_info_length += (int)strlen(block);
+		if (system_info_length < buflen) {
+			strcat(buffer, block);
+		}
 #endif
 #endif
+	}
+
+
 	/* Determine 32/64 bit data mode.
 	/* Determine 32/64 bit data mode.
 	 * see https://en.wikipedia.org/wiki/64-bit_computing */
 	 * see https://en.wikipedia.org/wiki/64-bit_computing */
-	fprintf(stdout,
-	        "Data model: i:%u/%u/%u/%u, f:%u/%u/%u, c:%u/%u, "
-	        "p:%u, s:%u, t:%u\n",
-	        (unsigned)sizeof(short),
-	        (unsigned)sizeof(int),
-	        (unsigned)sizeof(long),
-	        (unsigned)sizeof(long long),
-	        (unsigned)sizeof(float),
-	        (unsigned)sizeof(double),
-	        (unsigned)sizeof(long double),
-	        (unsigned)sizeof(char),
-	        (unsigned)sizeof(wchar_t),
-	        (unsigned)sizeof(void *),
-	        (unsigned)sizeof(size_t),
-	        (unsigned)sizeof(time_t));
+	{
+		mg_snprintf(NULL,
+		            NULL,
+		            block,
+		            sizeof(block),
+		            "Data model: int:%u/%u/%u/%u, float:%u/%u/%u, char:%u/%u, "
+		            "ptr:%u, size:%u, time:%u%s",
+		            (unsigned)sizeof(short),
+		            (unsigned)sizeof(int),
+		            (unsigned)sizeof(long),
+		            (unsigned)sizeof(long long),
+		            (unsigned)sizeof(float),
+		            (unsigned)sizeof(double),
+		            (unsigned)sizeof(long double),
+		            (unsigned)sizeof(char),
+		            (unsigned)sizeof(wchar_t),
+		            (unsigned)sizeof(void *),
+		            (unsigned)sizeof(size_t),
+		            (unsigned)sizeof(time_t),
+		            eol);
+		system_info_length += (int)strlen(block);
+		if (system_info_length < buflen) {
+			strcat(buffer, block);
+		}
+	}
+
+	return system_info_length;
+}
+
+
+/* Get system information. It can be printed or stored by the caller.
+ * Return the size of available information. */
+int
+mg_get_system_info(char *buffer, int buflen)
+{
+	if ((buffer == NULL) || (buflen < 1)) {
+		return mg_get_system_info_impl(NULL, 0);
+	} else {
+		/* Reset buffer, so we can always use strcat. */
+		buffer[0] = 0;
+		return mg_get_system_info_impl(buffer, buflen);
+	}
+}
+
+
+/* mg_init_library counter */
+static int mg_init_library_called = 0;
+
 
 
+/* Initialize this library. This function does not need to be thread safe. */
+unsigned
+mg_init_library(unsigned features)
+{
+	/* Currently we do nothing here. This is planned for Version 1.10.
+	 * For now, we just add this function, so clients can be changed early. */
+	if (mg_init_library_called <= 0) {
+		mg_init_library_called = 1;
+		return mg_check_feature(features & 0xFFu);
+	}
 	return 0;
 	return 0;
 }
 }
 
 
+
+/* Un-initialize this library. */
+unsigned
+mg_exit_library(void)
+{
+	if (mg_init_library_called <= 0) {
+		return 0;
+	}
+	mg_init_library_called--;
+	return 1;
+}
+
+
 /* End of civetweb.c */
 /* End of civetweb.c */

+ 2 - 1
src/civetweb_private_lua.h

@@ -5,6 +5,7 @@
 #ifndef CIVETWEB_PRIVATE_LUA_H
 #ifndef CIVETWEB_PRIVATE_LUA_H
 #define CIVETWEB_PRIVATE_LUA_H
 #define CIVETWEB_PRIVATE_LUA_H
 
 
-void civetweb_open_lua_libs(lua_State *L);
+int run_lua(const char *file_name);
+
 
 
 #endif
 #endif

+ 1 - 1
src/handle_form.inl

@@ -1,4 +1,4 @@
-/* Copyright (c) 2016 the Civetweb developers
+/* Copyright (c) 2016-2017 the Civetweb developers
  *
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
  * of this software and associated documentation files (the "Software"), to deal

+ 254 - 123
src/main.c

@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2016 the Civetweb developers
+/* Copyright (c) 2013-2017 the Civetweb developers
  * Copyright (c) 2004-2013 Sergey Lyubka
  * Copyright (c) 2004-2013 Sergey Lyubka
  *
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -103,7 +103,6 @@
 #if !defined(__MINGW32__)
 #if !defined(__MINGW32__)
 extern char *_getcwd(char *buf, size_t size);
 extern char *_getcwd(char *buf, size_t size);
 #endif
 #endif
-static int sGuard = 0; /* test if any dialog is already open */
 
 
 #ifndef PATH_MAX
 #ifndef PATH_MAX
 #define PATH_MAX MAX_PATH
 #define PATH_MAX MAX_PATH
@@ -149,6 +148,7 @@ static int g_exit_flag = 0;         /* Main loop should exit */
 static char g_server_base_name[40]; /* Set by init_server_name() */
 static char g_server_base_name[40]; /* Set by init_server_name() */
 static const char *g_server_name;   /* Set by init_server_name() */
 static const char *g_server_name;   /* Set by init_server_name() */
 static const char *g_icon_name;     /* Set by init_server_name() */
 static const char *g_icon_name;     /* Set by init_server_name() */
+static char *g_system_info;         /* Set by init_system_info() */
 static char g_config_file_name[PATH_MAX] =
 static char g_config_file_name[PATH_MAX] =
     "";                          /* Set by process_command_line_arguments() */
     "";                          /* Set by process_command_line_arguments() */
 static struct mg_context *g_ctx; /* Set by start_civetweb() */
 static struct mg_context *g_ctx; /* Set by start_civetweb() */
@@ -647,6 +647,26 @@ init_server_name(int argc, const char *argv[])
 }
 }
 
 
 
 
+static void
+init_system_info(void)
+{
+	int len = mg_get_system_info(NULL, 0);
+	if (len > 0) {
+		g_system_info = (char *)malloc((unsigned)len + 1);
+		(void)mg_get_system_info(g_system_info, len + 1);
+	} else {
+		g_system_info = sdup("Not available");
+	}
+}
+
+
+static void
+free_system_info(void)
+{
+	free(g_system_info);
+}
+
+
 static int
 static int
 log_message(const struct mg_connection *conn, const char *message)
 log_message(const struct mg_connection *conn, const char *message)
 {
 {
@@ -756,60 +776,8 @@ set_absolute_path(char *options[],
 
 
 #ifdef USE_LUA
 #ifdef USE_LUA
 
 
-#include "civetweb_lua.h"
 #include "civetweb_private_lua.h"
 #include "civetweb_private_lua.h"
 
 
-static int
-run_lua(const char *file_name)
-{
-	struct lua_State *L;
-	int lua_ret;
-	int func_ret = EXIT_FAILURE;
-	const char *lua_err_txt;
-
-#ifdef WIN32
-	(void)MakeConsole();
-#endif
-
-	L = luaL_newstate();
-	if (L == NULL) {
-		fprintf(stderr, "Error: Cannot create Lua state\n");
-		return EXIT_FAILURE;
-	}
-	civetweb_open_lua_libs(L);
-
-	lua_ret = luaL_loadfile(L, file_name);
-	if (lua_ret != LUA_OK) {
-		/* Error when loading the file (e.g. file not found, out of memory, ...)
-		 */
-		lua_err_txt = lua_tostring(L, -1);
-		fprintf(stderr, "Error loading file %s: %s\n", file_name, lua_err_txt);
-	} else {
-		/* The script file is loaded, now call it */
-		lua_ret = lua_pcall(L,
-		                    /* no arguments */ 0,
-		                    /* zero or one return value */ 1,
-		                    /* errors as strint return value */ 0);
-		if (lua_ret != LUA_OK) {
-			/* Error when executing the script */
-			lua_err_txt = lua_tostring(L, -1);
-			fprintf(stderr,
-			        "Error running file %s: %s\n",
-			        file_name,
-			        lua_err_txt);
-		} else {
-			/* Script executed */
-			if (lua_type(L, -1) == LUA_TNUMBER) {
-				func_ret = (int)lua_tonumber(L, -1);
-			} else {
-				func_ret = EXIT_SUCCESS;
-			}
-		}
-	}
-	lua_close(L);
-
-	return func_ret;
-}
 #endif
 #endif
 
 
 
 
@@ -822,10 +790,6 @@ run_duktape(const char *file_name)
 {
 {
 	duk_context *ctx = NULL;
 	duk_context *ctx = NULL;
 
 
-#ifdef WIN32
-	(void)MakeConsole();
-#endif
-
 	ctx = duk_create_heap_default();
 	ctx = duk_create_heap_default();
 	if (!ctx) {
 	if (!ctx) {
 		fprintf(stderr, "Failed to create a Duktape heap.\n");
 		fprintf(stderr, "Failed to create a Duktape heap.\n");
@@ -867,8 +831,11 @@ start_civetweb(int argc, char *argv[])
 #ifdef WIN32
 #ifdef WIN32
 		(void)MakeConsole();
 		(void)MakeConsole();
 #endif
 #endif
-		fprintf(stdout, "\n%s (%s)\n", g_server_base_name, g_server_name);
-		(void)mg_print_system_info(0, 0);
+		fprintf(stdout,
+		        "\n%s (%s)\n%s\n",
+		        g_server_base_name,
+		        g_server_name,
+		        g_system_info);
 
 
 		exit(EXIT_SUCCESS);
 		exit(EXIT_SUCCESS);
 	}
 	}
@@ -902,6 +869,9 @@ start_civetweb(int argc, char *argv[])
 		if (argc != 3) {
 		if (argc != 3) {
 			show_usage_and_exit(argv[0]);
 			show_usage_and_exit(argv[0]);
 		}
 		}
+#ifdef WIN32
+		(void)MakeConsole();
+#endif
 		exit(run_lua(argv[2]));
 		exit(run_lua(argv[2]));
 #else
 #else
 		show_server_name();
 		show_server_name();
@@ -917,6 +887,9 @@ start_civetweb(int argc, char *argv[])
 		if (argc != 3) {
 		if (argc != 3) {
 			show_usage_and_exit(argv[0]);
 			show_usage_and_exit(argv[0]);
 		}
 		}
+#ifdef WIN32
+		(void)MakeConsole();
+#endif
 		exit(run_duktape(argv[2]));
 		exit(run_duktape(argv[2]));
 #else
 #else
 		show_server_name();
 		show_server_name();
@@ -1039,7 +1012,6 @@ static SERVICE_STATUS_HANDLE hStatus;
 static const char *service_magic_argument = "--";
 static const char *service_magic_argument = "--";
 static NOTIFYICONDATA TrayIcon;
 static NOTIFYICONDATA TrayIcon;
 
 
-
 static void WINAPI
 static void WINAPI
 ControlHandler(DWORD code)
 ControlHandler(DWORD code)
 {
 {
@@ -1128,6 +1100,17 @@ save_config(HWND hDlg, FILE *fp)
 }
 }
 
 
 
 
+/* LPARAM pointer passed to WM_INITDIALOG */
+struct dlg_proc_param {
+	int guard;
+	HWND hWnd;
+	const char *name;
+	char *buffer;
+	unsigned buflen;
+};
+
+
+/* Dialog proc for settings dialog */
 static INT_PTR CALLBACK
 static INT_PTR CALLBACK
 SettingsDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
 SettingsDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
 {
 {
@@ -1137,7 +1120,7 @@ SettingsDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
 	const struct mg_option *default_options = mg_get_valid_options();
 	const struct mg_option *default_options = mg_get_valid_options();
 	char *file_options[MAX_OPTIONS * 2 + 1] = {0};
 	char *file_options[MAX_OPTIONS * 2 + 1] = {0};
 	char *title;
 	char *title;
-	(void)lParam;
+	struct dlg_proc_param *pdlg_proc_param;
 
 
 	switch (msg) {
 	switch (msg) {
 
 
@@ -1261,7 +1244,9 @@ SettingsDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
 	case WM_INITDIALOG:
 	case WM_INITDIALOG:
 		/* Store hWnd in a parameter accessible by the parent, so we can
 		/* Store hWnd in a parameter accessible by the parent, so we can
 		 * bring this window to front if required. */
 		 * bring this window to front if required. */
-		*((HWND *)lParam) = hDlg;
+		pdlg_proc_param = (struct dlg_proc_param *)lParam;
+		pdlg_proc_param->hWnd = hDlg;
+
 		/* Initialize the dialog elements */
 		/* Initialize the dialog elements */
 		SendMessage(hDlg, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)hIcon);
 		SendMessage(hDlg, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)hIcon);
 		SendMessage(hDlg, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)hIcon);
 		SendMessage(hDlg, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)hIcon);
@@ -1287,17 +1272,13 @@ SettingsDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
 }
 }
 
 
 
 
-struct tstring_input_buf {
-	unsigned buflen;
-	char *buffer;
-};
-
-
+/* Dialog proc for input dialog */
 static INT_PTR CALLBACK
 static INT_PTR CALLBACK
-InputDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lP)
+InputDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
 {
 {
-	static struct tstring_input_buf *inBuf = 0;
+	static struct dlg_proc_param *inBuf = 0;
 	WORD ctrlId;
 	WORD ctrlId;
+	HWND hIn;
 
 
 	switch (msg) {
 	switch (msg) {
 	case WM_CLOSE:
 	case WM_CLOSE:
@@ -1308,11 +1289,18 @@ InputDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lP)
 	case WM_COMMAND:
 	case WM_COMMAND:
 		ctrlId = LOWORD(wParam);
 		ctrlId = LOWORD(wParam);
 		if (ctrlId == IDOK) {
 		if (ctrlId == IDOK) {
-			/* Add user */
-			GetWindowText(GetDlgItem(hDlg, ID_INPUT_LINE),
-			              inBuf->buffer,
-			              (int)inBuf->buflen);
-			if (strlen(inBuf->buffer) > 0) {
+			/* Get handle of input line */
+			hIn = GetDlgItem(hDlg, ID_INPUT_LINE);
+
+			if (hIn) {
+				/* Get content of input line */
+				GetWindowText(hIn, inBuf->buffer, (int)inBuf->buflen);
+				if (strlen(inBuf->buffer) > 0) {
+					/* Input dialog is not empty. */
+					EndDialog(hDlg, IDOK);
+				}
+			} else {
+				/* There is no input line in this dialog. */
 				EndDialog(hDlg, IDOK);
 				EndDialog(hDlg, IDOK);
 			}
 			}
 		} else if (ctrlId == IDCANCEL) {
 		} else if (ctrlId == IDCANCEL) {
@@ -1321,17 +1309,30 @@ InputDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lP)
 		break;
 		break;
 
 
 	case WM_INITDIALOG:
 	case WM_INITDIALOG:
-		inBuf = (struct tstring_input_buf *)lP;
-		assert(inBuf != NULL);
-		assert((inBuf->buffer != NULL) && (inBuf->buflen != 0));
-		assert(strlen(inBuf->buffer) < inBuf->buflen);
-		SendMessage(hDlg, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)hIcon);
-		SendMessage(hDlg, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)hIcon);
-		SendDlgItemMessage(
-		    hDlg, ID_INPUT_LINE, EM_LIMITTEXT, inBuf->buflen - 1, 0);
-		SetWindowText(GetDlgItem(hDlg, ID_INPUT_LINE), inBuf->buffer);
-		SetWindowText(hDlg, "Modify password");
-		SetFocus(GetDlgItem(hDlg, ID_INPUT_LINE));
+		/* Get handle of input line */
+		hIn = GetDlgItem(hDlg, ID_INPUT_LINE);
+
+		/* Get dialog parameters */
+		inBuf = (struct dlg_proc_param *)lParam;
+
+		/* Set dialog handle for the caller */
+		inBuf->hWnd = hDlg;
+
+		/* Set dialog name */
+		SetWindowText(hDlg, inBuf->name);
+
+		if (hIn) {
+			/* This is an input dialog */
+			assert(inBuf != NULL);
+			assert((inBuf->buffer != NULL) && (inBuf->buflen != 0));
+			assert(strlen(inBuf->buffer) < inBuf->buflen);
+			SendMessage(hDlg, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)hIcon);
+			SendMessage(hDlg, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)hIcon);
+			SendMessage(hIn, EM_LIMITTEXT, inBuf->buflen - 1, 0);
+			SetWindowText(hIn, inBuf->buffer);
+			SetFocus(hIn);
+		}
+
 		break;
 		break;
 
 
 	default:
 	default:
@@ -1392,7 +1393,7 @@ get_password(const char *user,
 	DLGTEMPLATE *dia = (DLGTEMPLATE *)mem;
 	DLGTEMPLATE *dia = (DLGTEMPLATE *)mem;
 	int ok;
 	int ok;
 	short y;
 	short y;
-	struct tstring_input_buf dlgprms;
+	static struct dlg_proc_param s_dlg_proc_param;
 
 
 	static struct {
 	static struct {
 		DLGTEMPLATE template; /* 18 bytes */
 		DLGTEMPLATE template; /* 18 bytes */
@@ -1414,14 +1415,20 @@ get_password(const char *user,
 	                   8,
 	                   8,
 	                   L"Tahoma"};
 	                   L"Tahoma"};
 
 
-	dlgprms.buffer = passwd;
-	dlgprms.buflen = passwd_len;
-
 	assert((user != NULL) && (realm != NULL) && (passwd != NULL));
 	assert((user != NULL) && (realm != NULL) && (passwd != NULL));
 
 
-	if (sGuard < 100) {
-		sGuard += 100;
+	/* Only allow one instance of this dialog to be open. */
+	if (s_dlg_proc_param.guard == 0) {
+		memset(&s_dlg_proc_param, 0, sizeof(s_dlg_proc_param));
+		s_dlg_proc_param.guard = 1;
 	} else {
 	} else {
+		SetForegroundWindow(s_dlg_proc_param.hWnd);
+		return 0;
+	}
+
+	/* Do not open a password dialog, if the username is empty */
+	if (user[0] == 0) {
+		s_dlg_proc_param.guard = 0;
 		return 0;
 		return 0;
 	}
 	}
 
 
@@ -1429,6 +1436,10 @@ get_password(const char *user,
 	memset(passwd, 0, passwd_len);
 	memset(passwd, 0, passwd_len);
 	suggest_passwd(passwd);
 	suggest_passwd(passwd);
 
 
+	/* Make buffer available for input dialog */
+	s_dlg_proc_param.buffer = passwd;
+	s_dlg_proc_param.buflen = passwd_len;
+
 	/* Create the dialog */
 	/* Create the dialog */
 	(void)memset(mem, 0, sizeof(mem));
 	(void)memset(mem, 0, sizeof(mem));
 	(void)memcpy(mem, &dialog_header, sizeof(dialog_header));
 	(void)memcpy(mem, &dialog_header, sizeof(dialog_header));
@@ -1450,7 +1461,7 @@ get_password(const char *user,
 	            0x81,
 	            0x81,
 	            ID_CONTROLS + 1,
 	            ID_CONTROLS + 1,
 	            WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL
 	            WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL
-	                | WS_DISABLED,
+	                | ES_READONLY,
 	            15 + LABEL_WIDTH,
 	            15 + LABEL_WIDTH,
 	            y,
 	            y,
 	            WIDTH - LABEL_WIDTH - 25,
 	            WIDTH - LABEL_WIDTH - 25,
@@ -1473,7 +1484,7 @@ get_password(const char *user,
 	            0x81,
 	            0x81,
 	            ID_CONTROLS + 2,
 	            ID_CONTROLS + 2,
 	            WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL
 	            WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL
-	                | WS_DISABLED,
+	                | ES_READONLY,
 	            15 + LABEL_WIDTH,
 	            15 + LABEL_WIDTH,
 	            y,
 	            y,
 	            WIDTH - LABEL_WIDTH - 25,
 	            WIDTH - LABEL_WIDTH - 25,
@@ -1528,10 +1539,14 @@ get_password(const char *user,
 
 
 	dia->cy = y + (WORD)(HEIGHT * 1.5);
 	dia->cy = y + (WORD)(HEIGHT * 1.5);
 
 
-	ok = (IDOK == DialogBoxIndirectParam(
-	                  NULL, dia, NULL, InputDlgProc, (LPARAM)&dlgprms));
+	s_dlg_proc_param.name = "Modify password";
 
 
-	sGuard -= 100;
+	ok =
+	    (IDOK == DialogBoxIndirectParam(
+	                 NULL, dia, NULL, InputDlgProc, (LPARAM)&s_dlg_proc_param));
+
+	s_dlg_proc_param.hWnd = NULL;
+	s_dlg_proc_param.guard = 0;
 
 
 	return ok;
 	return ok;
 
 
@@ -1541,12 +1556,14 @@ get_password(const char *user,
 }
 }
 
 
 
 
+/* Dialog proc for password dialog */
 static INT_PTR CALLBACK
 static INT_PTR CALLBACK
-PasswordDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lP)
+PasswordDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
 {
 {
 	static const char *passfile = 0;
 	static const char *passfile = 0;
 	char domain[256], user[256], password[256];
 	char domain[256], user[256], password[256];
 	WORD ctrlId;
 	WORD ctrlId;
+	struct dlg_proc_param *pdlg_proc_param;
 
 
 	switch (msg) {
 	switch (msg) {
 	case WM_CLOSE:
 	case WM_CLOSE:
@@ -1596,7 +1613,9 @@ PasswordDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lP)
 		break;
 		break;
 
 
 	case WM_INITDIALOG:
 	case WM_INITDIALOG:
-		passfile = (const char *)lP;
+		pdlg_proc_param = (struct dlg_proc_param *)lParam;
+		pdlg_proc_param->hWnd = hDlg;
+		passfile = pdlg_proc_param->name;
 		SendMessage(hDlg, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)hIcon);
 		SendMessage(hDlg, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)hIcon);
 		SendMessage(hDlg, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)hIcon);
 		SendMessage(hDlg, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)hIcon);
 		SetWindowText(hDlg, passfile);
 		SetWindowText(hDlg, passfile);
@@ -1667,7 +1686,7 @@ show_settings_dialog()
 	DLGTEMPLATE *dia = (DLGTEMPLATE *)mem;
 	DLGTEMPLATE *dia = (DLGTEMPLATE *)mem;
 	WORD i, cl, nelems = 0;
 	WORD i, cl, nelems = 0;
 	short width, x, y;
 	short width, x, y;
-	static HWND sDlgHWnd;
+	static struct dlg_proc_param s_dlg_proc_param;
 
 
 	static struct {
 	static struct {
 		DLGTEMPLATE template; /* 18 bytes */
 		DLGTEMPLATE template; /* 18 bytes */
@@ -1689,10 +1708,11 @@ show_settings_dialog()
 	                   8,
 	                   8,
 	                   L"Tahoma"};
 	                   L"Tahoma"};
 
 
-	if (sGuard == 0) {
-		sGuard++;
+	if (s_dlg_proc_param.guard == 0) {
+		memset(&s_dlg_proc_param, 0, sizeof(s_dlg_proc_param));
+		s_dlg_proc_param.guard = 1;
 	} else {
 	} else {
-		SetForegroundWindow(sDlgHWnd);
+		SetForegroundWindow(s_dlg_proc_param.hWnd);
 		return;
 		return;
 	}
 	}
 
 
@@ -1823,9 +1843,12 @@ show_settings_dialog()
 	assert(((intptr_t)p - (intptr_t)mem) < (intptr_t)sizeof(mem));
 	assert(((intptr_t)p - (intptr_t)mem) < (intptr_t)sizeof(mem));
 
 
 	dia->cy = ((nelems + 1) / 2 + 1) * HEIGHT + 30;
 	dia->cy = ((nelems + 1) / 2 + 1) * HEIGHT + 30;
-	DialogBoxIndirectParam(NULL, dia, NULL, SettingsDlgProc, (LPARAM)&sDlgHWnd);
-	sGuard--;
-	sDlgHWnd = NULL;
+
+	DialogBoxIndirectParam(
+	    NULL, dia, NULL, SettingsDlgProc, (LPARAM)&s_dlg_proc_param);
+
+	s_dlg_proc_param.hWnd = NULL;
+	s_dlg_proc_param.guard = 0;
 
 
 #undef HEIGHT
 #undef HEIGHT
 #undef WIDTH
 #undef WIDTH
@@ -1849,6 +1872,7 @@ change_password_file()
 	unsigned char mem[4096], *p;
 	unsigned char mem[4096], *p;
 	DLGTEMPLATE *dia = (DLGTEMPLATE *)mem;
 	DLGTEMPLATE *dia = (DLGTEMPLATE *)mem;
 	const char *domain = mg_get_option(g_ctx, "authentication_domain");
 	const char *domain = mg_get_option(g_ctx, "authentication_domain");
+	static struct dlg_proc_param s_dlg_proc_param;
 
 
 	static struct {
 	static struct {
 		DLGTEMPLATE template; /* 18 bytes */
 		DLGTEMPLATE template; /* 18 bytes */
@@ -1870,9 +1894,11 @@ change_password_file()
 	                   8,
 	                   8,
 	                   L"Tahoma"};
 	                   L"Tahoma"};
 
 
-	if (sGuard == 0) {
-		sGuard++;
+	if (s_dlg_proc_param.guard == 0) {
+		memset(&s_dlg_proc_param, 0, sizeof(s_dlg_proc_param));
+		s_dlg_proc_param.guard = 1;
 	} else {
 	} else {
+		SetForegroundWindow(s_dlg_proc_param.hWnd);
 		return;
 		return;
 	}
 	}
 
 
@@ -1885,7 +1911,7 @@ change_password_file()
 	of.Flags = OFN_CREATEPROMPT | OFN_NOCHANGEDIR | OFN_HIDEREADONLY;
 	of.Flags = OFN_CREATEPROMPT | OFN_NOCHANGEDIR | OFN_HIDEREADONLY;
 
 
 	if (IDOK != GetSaveFileName(&of)) {
 	if (IDOK != GetSaveFileName(&of)) {
-		sGuard--;
+		s_dlg_proc_param.guard = 0;
 		return;
 		return;
 	}
 	}
 
 
@@ -1894,11 +1920,12 @@ change_password_file()
 		fclose(f);
 		fclose(f);
 	} else {
 	} else {
 		MessageBox(NULL, path, "Can not open file", MB_ICONERROR);
 		MessageBox(NULL, path, "Can not open file", MB_ICONERROR);
-		sGuard--;
+		s_dlg_proc_param.guard = 0;
 		return;
 		return;
 	}
 	}
 
 
 	do {
 	do {
+		s_dlg_proc_param.hWnd = NULL;
 		(void)memset(mem, 0, sizeof(mem));
 		(void)memset(mem, 0, sizeof(mem));
 		(void)memcpy(mem, &dialog_header, sizeof(dialog_header));
 		(void)memcpy(mem, &dialog_header, sizeof(dialog_header));
 		p = mem + sizeof(dialog_header);
 		p = mem + sizeof(dialog_header);
@@ -1906,7 +1933,7 @@ change_password_file()
 		f = fopen(path, "r+");
 		f = fopen(path, "r+");
 		if (!f) {
 		if (!f) {
 			MessageBox(NULL, path, "Can not open file", MB_ICONERROR);
 			MessageBox(NULL, path, "Can not open file", MB_ICONERROR);
-			sGuard--;
+			s_dlg_proc_param.guard = 0;
 			return;
 			return;
 		}
 		}
 
 
@@ -1943,7 +1970,7 @@ change_password_file()
 			            0x81,
 			            0x81,
 			            ID_CONTROLS + nelems + ID_FILE_BUTTONS_DELTA,
 			            ID_CONTROLS + nelems + ID_FILE_BUTTONS_DELTA,
 			            WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL
 			            WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL
-			                | WS_DISABLED,
+			                | ES_READONLY,
 			            245,
 			            245,
 			            y,
 			            y,
 			            60,
 			            60,
@@ -1954,7 +1981,7 @@ change_password_file()
 			            0x81,
 			            0x81,
 			            ID_CONTROLS + nelems,
 			            ID_CONTROLS + nelems,
 			            WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL
 			            WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL
-			                | WS_DISABLED,
+			                | ES_READONLY,
 			            140,
 			            140,
 			            y,
 			            y,
 			            100,
 			            100,
@@ -2027,11 +2054,18 @@ change_password_file()
 		assert(((intptr_t)p - (intptr_t)mem) < (intptr_t)sizeof(mem));
 		assert(((intptr_t)p - (intptr_t)mem) < (intptr_t)sizeof(mem));
 
 
 		dia->cy = y + 20;
 		dia->cy = y + 20;
-	} while ((IDOK == DialogBoxIndirectParam(
-	                      NULL, dia, NULL, PasswordDlgProc, (LPARAM)path))
+
+		s_dlg_proc_param.name = path;
+
+	} while ((IDOK == DialogBoxIndirectParam(NULL,
+	                                         dia,
+	                                         NULL,
+	                                         PasswordDlgProc,
+	                                         (LPARAM)&s_dlg_proc_param))
 	         && (!g_exit_flag));
 	         && (!g_exit_flag));
 
 
-	sGuard--;
+	s_dlg_proc_param.hWnd = NULL;
+	s_dlg_proc_param.guard = 0;
 
 
 #undef HEIGHT
 #undef HEIGHT
 #undef WIDTH
 #undef WIDTH
@@ -2039,18 +2073,106 @@ change_password_file()
 }
 }
 
 
 
 
-static void
+int
 show_system_info()
 show_system_info()
 {
 {
-	if (sGuard == 0) {
-		sGuard++;
+#define HEIGHT (15)
+#define WIDTH (320)
+#define LABEL_WIDTH (50)
+
+	unsigned char mem[4096], *p;
+	DLGTEMPLATE *dia = (DLGTEMPLATE *)mem;
+	int ok;
+	short y;
+	static struct dlg_proc_param s_dlg_proc_param;
+
+	static struct {
+		DLGTEMPLATE template; /* 18 bytes */
+		WORD menu, class;
+		wchar_t caption[1];
+		WORD fontsiz;
+		wchar_t fontface[7];
+	} dialog_header = {{WS_CAPTION | WS_POPUP | WS_SYSMENU | WS_VISIBLE
+	                        | DS_SETFONT | WS_DLGFRAME,
+	                    WS_EX_TOOLWINDOW,
+	                    0,
+	                    200,
+	                    200,
+	                    WIDTH,
+	                    0},
+	                   0,
+	                   0,
+	                   L"",
+	                   8,
+	                   L"Tahoma"};
+
+	/* Only allow one instance of this dialog to be open. */
+	if (s_dlg_proc_param.guard == 0) {
+		memset(&s_dlg_proc_param, 0, sizeof(s_dlg_proc_param));
+		s_dlg_proc_param.guard = 1;
 	} else {
 	} else {
-		return;
+		SetForegroundWindow(s_dlg_proc_param.hWnd);
+		return 0;
 	}
 	}
 
 
-	/* TODO */
+	/* Create the dialog */
+	(void)memset(mem, 0, sizeof(mem));
+	(void)memcpy(mem, &dialog_header, sizeof(dialog_header));
+	p = mem + sizeof(dialog_header);
 
 
-	sGuard--;
+	y = HEIGHT;
+	add_control(&p,
+	            dia,
+	            0x82,
+	            ID_STATIC,
+	            WS_VISIBLE | WS_CHILD,
+	            10,
+	            y,
+	            LABEL_WIDTH,
+	            HEIGHT,
+	            "System Information:");
+	add_control(&p,
+	            dia,
+	            0x81,
+	            ID_CONTROLS + 1,
+	            WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL
+	                | ES_AUTOVSCROLL | ES_MULTILINE | ES_READONLY,
+	            15 + LABEL_WIDTH,
+	            y,
+	            WIDTH - LABEL_WIDTH - 25,
+	            HEIGHT * 7,
+	            g_system_info);
+
+	y += (WORD)(HEIGHT * 8);
+	add_control(&p,
+	            dia,
+	            0x80,
+	            IDOK,
+	            WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP,
+	            (WIDTH - 55) / 2,
+	            y,
+	            55,
+	            12,
+	            "Ok");
+
+	assert((intptr_t)p - (intptr_t)mem < (intptr_t)sizeof(mem));
+
+	dia->cy = y + (WORD)(HEIGHT * 1.5);
+
+	s_dlg_proc_param.name = "System information";
+
+	ok =
+	    (IDOK == DialogBoxIndirectParam(
+	                 NULL, dia, NULL, InputDlgProc, (LPARAM)&s_dlg_proc_param));
+
+	s_dlg_proc_param.hWnd = NULL;
+	s_dlg_proc_param.guard = 0;
+
+	return ok;
+
+#undef HEIGHT
+#undef WIDTH
+#undef LABEL_WIDTH
 }
 }
 
 
 
 
@@ -2115,6 +2237,7 @@ manage_service(int action)
 }
 }
 
 
 
 
+/* Window proc for taskbar icon */
 static LRESULT CALLBACK
 static LRESULT CALLBACK
 WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
 WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
 {
 {
@@ -2289,6 +2412,7 @@ WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR cmdline, int show)
 	(void)show;
 	(void)show;
 
 
 	init_server_name((int)__argc, (const char **)__argv);
 	init_server_name((int)__argc, (const char **)__argv);
+	init_system_info();
 	memset(&cls, 0, sizeof(cls));
 	memset(&cls, 0, sizeof(cls));
 	cls.lpfnWndProc = (WNDPROC)WindowProc;
 	cls.lpfnWndProc = (WNDPROC)WindowProc;
 	cls.hIcon = LoadIcon(NULL, IDI_APPLICATION);
 	cls.hIcon = LoadIcon(NULL, IDI_APPLICATION);
@@ -2334,6 +2458,8 @@ WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR cmdline, int show)
 		DispatchMessage(&msg);
 		DispatchMessage(&msg);
 	}
 	}
 
 
+	free_system_info();
+
 	/* Return the WM_QUIT value. */
 	/* Return the WM_QUIT value. */
 	return (int)msg.wParam;
 	return (int)msg.wParam;
 }
 }
@@ -2389,6 +2515,7 @@ int
 main(int argc, char *argv[])
 main(int argc, char *argv[])
 {
 {
 	init_server_name(argc, (const char **)argv);
 	init_server_name(argc, (const char **)argv);
+	init_system_info();
 	start_civetweb(argc, argv);
 	start_civetweb(argc, argv);
 
 
 	[NSAutoreleasePool new];
 	[NSAutoreleasePool new];
@@ -2446,6 +2573,7 @@ main(int argc, char *argv[])
 	[NSApp run];
 	[NSApp run];
 
 
 	stop_civetweb();
 	stop_civetweb();
+	free_system_info();
 
 
 	return EXIT_SUCCESS;
 	return EXIT_SUCCESS;
 }
 }
@@ -2456,6 +2584,7 @@ int
 main(int argc, char *argv[])
 main(int argc, char *argv[])
 {
 {
 	init_server_name(argc, (const char **)argv);
 	init_server_name(argc, (const char **)argv);
+	init_system_info();
 	start_civetweb(argc, argv);
 	start_civetweb(argc, argv);
 	fprintf(stdout,
 	fprintf(stdout,
 	        "%s started on port(s) %s with web root [%s]\n",
 	        "%s started on port(s) %s with web root [%s]\n",
@@ -2472,6 +2601,8 @@ main(int argc, char *argv[])
 	stop_civetweb();
 	stop_civetweb();
 	fprintf(stdout, "%s", " done.\n");
 	fprintf(stdout, "%s", " done.\n");
 
 
+	free_system_info();
+
 	return EXIT_SUCCESS;
 	return EXIT_SUCCESS;
 }
 }
 #endif /* _WIN32 */
 #endif /* _WIN32 */

+ 1 - 1
src/mod_duktape.inl

@@ -1,6 +1,6 @@
 /* This file is part of the CivetWeb web server.
 /* This file is part of the CivetWeb web server.
  * See https://github.com/civetweb/civetweb/
  * See https://github.com/civetweb/civetweb/
- * (C) 2015 by the CivetWeb authors, MIT license.
+ * (C) 2015-2017 by the CivetWeb authors, MIT license.
  */
  */
 
 
 #include "duktape.h"
 #include "duktape.h"

+ 94 - 1
src/mod_lua.inl

@@ -1,3 +1,7 @@
+/* This file is part of the CivetWeb web server.
+ * See https://github.com/civetweb/civetweb/
+ */
+
 #include "civetweb_lua.h"
 #include "civetweb_lua.h"
 #include "civetweb_private_lua.h"
 #include "civetweb_private_lua.h"
 
 
@@ -785,7 +789,7 @@ lsp_url_decode(lua_State *L)
 		text = lua_tolstring(L, 1, &text_len);
 		text = lua_tolstring(L, 1, &text_len);
 		is_form = (num_args == 2) ? lua_isboolean(L, 2) : 0;
 		is_form = (num_args == 2) ? lua_isboolean(L, 2) : 0;
 		if (text) {
 		if (text) {
-			mg_url_decode(text, text_len, dst, (int)sizeof(dst), is_form);
+			mg_url_decode(text, (int)text_len, dst, (int)sizeof(dst), is_form);
 			lua_pushstring(L, dst);
 			lua_pushstring(L, dst);
 		} else {
 		} else {
 			lua_pushnil(L);
 			lua_pushnil(L);
@@ -1264,6 +1268,10 @@ prepare_lua_request_info(struct mg_connection *conn, lua_State *L)
 	reg_int(L, "num_headers", conn->request_info.num_headers);
 	reg_int(L, "num_headers", conn->request_info.num_headers);
 	reg_int(L, "server_port", ntohs(conn->client.lsa.sin.sin_port));
 	reg_int(L, "server_port", ntohs(conn->client.lsa.sin.sin_port));
 
 
+	if (conn->path_info != NULL) {
+		reg_string(L, "path_info", conn->path_info);
+	}
+
 	if (conn->request_info.content_length >= 0) {
 	if (conn->request_info.content_length >= 0) {
 		/* reg_int64: content_length */
 		/* reg_int64: content_length */
 		lua_pushstring(L, "content_length");
 		lua_pushstring(L, "content_length");
@@ -1883,6 +1891,90 @@ lua_websocket_close(struct mg_connection *conn, void *ws_arg)
 }
 }
 #endif
 #endif
 
 
+
+lua_State *
+mg_prepare_lua_context_script(const char *file_name,
+                              struct mg_context *ctx,
+                              char *ebuf,
+                              size_t ebuf_len)
+{
+	struct lua_State *L;
+	int lua_ret;
+	const char *lua_err_txt;
+
+	L = luaL_newstate();
+	if (L == NULL) {
+		mg_snprintf(NULL,
+		            NULL, /* No truncation check for ebuf */
+		            ebuf,
+		            ebuf_len,
+		            "Error: %s",
+		            "Cannot create Lua state");
+		return 0;
+	}
+	civetweb_open_lua_libs(L);
+
+	lua_ret = luaL_loadfile(L, file_name);
+	if (lua_ret != LUA_OK) {
+		/* Error when loading the file (e.g. file not found, out of memory, ...)
+		 */
+		lua_err_txt = lua_tostring(L, -1);
+		mg_snprintf(NULL,
+		            NULL, /* No truncation check for ebuf */
+		            ebuf,
+		            ebuf_len,
+		            "Error loading file %s: %s\n",
+		            file_name,
+		            lua_err_txt);
+		return 0;
+	}
+
+	/* The script file is loaded, now call it */
+	lua_ret = lua_pcall(L,
+	                    /* no arguments */ 0,
+	                    /* zero or one return value */ 1,
+	                    /* errors as strint return value */ 0);
+
+	if (lua_ret != LUA_OK) {
+		/* Error when executing the script */
+		lua_err_txt = lua_tostring(L, -1);
+		mg_snprintf(NULL,
+		            NULL, /* No truncation check for ebuf */
+		            ebuf,
+		            ebuf_len,
+		            "Error running file %s: %s\n",
+		            file_name,
+		            lua_err_txt);
+		return 0;
+	}
+	/*	lua_close(L); must be done somewhere else */
+
+	return L;
+}
+
+
+int
+run_lua(const char *file_name)
+{
+	int func_ret = EXIT_FAILURE;
+	char ebuf[512] = {0};
+	lua_State *L =
+	    mg_prepare_lua_context_script(file_name, NULL, ebuf, sizeof(ebuf));
+	if (L) {
+		/* Script executed */
+		if (lua_type(L, -1) == LUA_TNUMBER) {
+			func_ret = (int)lua_tonumber(L, -1);
+		} else {
+			func_ret = EXIT_SUCCESS;
+		}
+		lua_close(L);
+	} else {
+		fprintf(stderr, "%s\n", ebuf);
+	}
+	return func_ret;
+}
+
+
 static void *lib_handle_uuid = NULL;
 static void *lib_handle_uuid = NULL;
 
 
 static void
 static void
@@ -1896,6 +1988,7 @@ lua_init_optional_libraries(void)
 #endif
 #endif
 }
 }
 
 
+
 static void
 static void
 lua_exit_optional_libraries(void)
 lua_exit_optional_libraries(void)
 {
 {

+ 4 - 4
src/third_party/LuaXML_lib.c

@@ -304,11 +304,11 @@ int Xml_eval(lua_State *L) {
 
 
     while((token=Tokenizer_next(tok))!=0) if(token[0]==OPN) { // new tag found
     while((token=Tokenizer_next(tok))!=0) if(token[0]==OPN) { // new tag found
         if(lua_gettop(L)) {
         if(lua_gettop(L)) {
-            int newIndex=lua_rawlen(L,-1)+1;
-            lua_pushnumber(L,newIndex);
+            size_t newIndex=lua_rawlen(L,-1)+1;
+            lua_pushnumber(L, (lua_Number)newIndex);
             lua_newtable(L);
             lua_newtable(L);
             lua_settable(L, -3);
             lua_settable(L, -3);
-            lua_pushnumber(L,newIndex);
+            lua_pushnumber(L, (lua_Number)newIndex);
             lua_gettable(L,-2);
             lua_gettable(L,-2);
         }
         }
         else {
         else {
@@ -360,7 +360,7 @@ int Xml_eval(lua_State *L) {
         else break;
         else break;
     }
     }
     else { // read elements
     else { // read elements
-        lua_pushnumber(L,lua_rawlen(L,-1)+1);
+        lua_pushnumber(L,(lua_Number)lua_rawlen(L,-1)+1);
         Xml_pushDecode(L, token, 0);
         Xml_pushDecode(L, token, 0);
         lua_settable(L, -3);
         lua_settable(L, -3);
     }
     }

+ 1 - 1
src/third_party/civetweb_lua.h

@@ -1,4 +1,4 @@
-/* Copyright (c) 2015 the Civetweb developers
+/* Copyright (c) 2015-2017 the Civetweb developers
  *
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
  * of this software and associated documentation files (the "Software"), to deal

+ 65 - 24
src/timer.inl

@@ -1,3 +1,7 @@
+/* This file is part of the CivetWeb web server.
+ * See https://github.com/civetweb/civetweb/
+ * (C) 2014-2017 by the CivetWeb authors, MIT license.
+ */
 
 
 #if !defined(MAX_TIMERS)
 #if !defined(MAX_TIMERS)
 #define MAX_TIMERS MAX_WORKER_THREADS
 #define MAX_TIMERS MAX_WORKER_THREADS
@@ -20,6 +24,33 @@ struct ttimers {
 };
 };
 
 
 
 
+static double
+timer_getcurrenttime(void)
+{
+#if defined(_WIN32)
+	/* GetTickCount returns milliseconds since system start as
+	 * unsigned 32 bit value. It will wrap around every 49.7 days.
+	 * We need to use a 64 bit counter (will wrap in 500 mio. years),
+	 * by adding the 32 bit difference since the last call to a
+	 * 64 bit counter. This algorithm will only work, if this
+	 * function is called at least once every 7 weeks. */
+	static DWORD last_tick;
+	static uint64_t now_tick64;
+
+	DWORD now_tick = GetTickCount();
+
+	now_tick64 += ((DWORD)(now_tick - last_tick));
+	last_tick = now_tick;
+	return (double)now_tick64 * 1.0E-3;
+#else
+	struct timespec now_ts;
+
+	clock_gettime(CLOCK_MONOTONIC, &now_ts);
+	return (double)now_ts.tv_sec + (double)now_ts.tv_nsec * 1.0E-9;
+#endif
+}
+
+
 static int
 static int
 timer_add(struct mg_context *ctx,
 timer_add(struct mg_context *ctx,
           double next_time,
           double next_time,
@@ -30,16 +61,13 @@ timer_add(struct mg_context *ctx,
 {
 {
 	unsigned u, v;
 	unsigned u, v;
 	int error = 0;
 	int error = 0;
-	struct timespec now;
-	double dt; /* double time */
+	double now;
 
 
 	if (ctx->stop_flag) {
 	if (ctx->stop_flag) {
 		return 0;
 		return 0;
 	}
 	}
 
 
-	clock_gettime(CLOCK_MONOTONIC, &now);
-	dt = (double)(now.tv_sec);
-	dt += (double)(now.tv_nsec) * 1.0E-9;
+	now = timer_getcurrenttime();
 
 
 	/* HCP24: if is_relative = 0 and next_time < now
 	/* HCP24: if is_relative = 0 and next_time < now
 	 *        action will be called so fast as possible
 	 *        action will be called so fast as possible
@@ -49,19 +77,26 @@ timer_add(struct mg_context *ctx,
 	 *        then the period is working
 	 *        then the period is working
 	 * Solution:
 	 * Solution:
 	 *        if next_time < now then we set next_time = now.
 	 *        if next_time < now then we set next_time = now.
-	 *        The first callback will be so fast as possible  (now)
+	 *        The first callback will be so fast as possible (now)
 	 *        but the next callback on period
 	 *        but the next callback on period
 	*/
 	*/
 	if (is_relative) {
 	if (is_relative) {
-		next_time += dt;
-	} else if (next_time < dt) {
-		next_time = dt;
+		next_time += now;
+	}
+
+	/* You can not set timers into the past */
+	if (next_time < now) {
+		next_time = now;
 	}
 	}
 
 
 	pthread_mutex_lock(&ctx->timers->mutex);
 	pthread_mutex_lock(&ctx->timers->mutex);
 	if (ctx->timers->timer_count == MAX_TIMERS) {
 	if (ctx->timers->timer_count == MAX_TIMERS) {
 		error = 1;
 		error = 1;
 	} else {
 	} else {
+		/* Insert new timer into a sorted list. */
+		/* The linear list is still most efficient for short lists (small
+		 * number of timers) - if there are many timers, different
+		 * algorithms will work better. */
 		for (u = 0; u < ctx->timers->timer_count; u++) {
 		for (u = 0; u < ctx->timers->timer_count; u++) {
 			if (ctx->timers->timers[u].time > next_time) {
 			if (ctx->timers->timers[u].time > next_time) {
 				/* HCP24: moving all timers > next_time */
 				/* HCP24: moving all timers > next_time */
@@ -86,7 +121,6 @@ static void
 timer_thread_run(void *thread_func_param)
 timer_thread_run(void *thread_func_param)
 {
 {
 	struct mg_context *ctx = (struct mg_context *)thread_func_param;
 	struct mg_context *ctx = (struct mg_context *)thread_func_param;
-	struct timespec now;
 	double d;
 	double d;
 	unsigned u;
 	unsigned u;
 	int re_schedule;
 	int re_schedule;
@@ -99,18 +133,12 @@ timer_thread_run(void *thread_func_param)
 		ctx->callbacks.init_thread(ctx, 2);
 		ctx->callbacks.init_thread(ctx, 2);
 	}
 	}
 
 
-#if defined(HAVE_CLOCK_NANOSLEEP) /* Linux with librt */
-	/* TODO */
-	while (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &request, &request)
-	       == EINTR) { /*nop*/
-		;
-	}
-#else
-	clock_gettime(CLOCK_MONOTONIC, &now);
-	d = (double)now.tv_sec + (double)now.tv_nsec * 1.0E-9;
+	d = timer_getcurrenttime();
+
 	while (ctx->stop_flag == 0) {
 	while (ctx->stop_flag == 0) {
 		pthread_mutex_lock(&ctx->timers->mutex);
 		pthread_mutex_lock(&ctx->timers->mutex);
-		if (ctx->timers->timer_count > 0 && d >= ctx->timers->timers[0].time) {
+		if ((ctx->timers->timer_count > 0)
+		    && (d >= ctx->timers->timers[0].time)) {
 			t = ctx->timers->timers[0];
 			t = ctx->timers->timers[0];
 			for (u = 1; u < ctx->timers->timer_count; u++) {
 			for (u = 1; u < ctx->timers->timer_count; u++) {
 				ctx->timers->timers[u - 1] = ctx->timers->timers[u];
 				ctx->timers->timers[u - 1] = ctx->timers->timers[u];
@@ -125,11 +153,20 @@ timer_thread_run(void *thread_func_param)
 		} else {
 		} else {
 			pthread_mutex_unlock(&ctx->timers->mutex);
 			pthread_mutex_unlock(&ctx->timers->mutex);
 		}
 		}
-		mg_sleep(1);
-		clock_gettime(CLOCK_MONOTONIC, &now);
-		d = (double)now.tv_sec + (double)now.tv_nsec * 1.0E-9;
-	}
+
+/* 10 ms seems reasonable.
+ * A faster loop (smaller sleep value) increases CPU load,
+ * a slower loop (higher sleep value) decreases timer accuracy.
+ */
+#ifdef _WIN32
+		Sleep(10);
+#else
+		usleep(10000);
 #endif
 #endif
+
+		d = timer_getcurrenttime();
+	}
+	ctx->timers->timer_count = 0;
 }
 }
 
 
 
 
@@ -155,6 +192,8 @@ timers_init(struct mg_context *ctx)
 	ctx->timers = (struct ttimers *)mg_calloc(sizeof(struct ttimers), 1);
 	ctx->timers = (struct ttimers *)mg_calloc(sizeof(struct ttimers), 1);
 	(void)pthread_mutex_init(&ctx->timers->mutex, NULL);
 	(void)pthread_mutex_init(&ctx->timers->mutex, NULL);
 
 
+	(void)timer_getcurrenttime();
+
 	/* Start timer thread */
 	/* Start timer thread */
 	mg_start_thread_with_id(timer_thread, ctx, &ctx->timers->threadid);
 	mg_start_thread_with_id(timer_thread, ctx, &ctx->timers->threadid);
 
 
@@ -166,6 +205,8 @@ static void
 timers_exit(struct mg_context *ctx)
 timers_exit(struct mg_context *ctx)
 {
 {
 	if (ctx->timers) {
 	if (ctx->timers) {
+		pthread_mutex_lock(&ctx->timers->mutex);
+		ctx->timers->timer_count = 0;
 		(void)pthread_mutex_destroy(&ctx->timers->mutex);
 		(void)pthread_mutex_destroy(&ctx->timers->mutex);
 		mg_free(ctx->timers);
 		mg_free(ctx->timers);
 	}
 	}

+ 12 - 10
test/CMakeLists.txt

@@ -7,15 +7,15 @@ endif()
 
 
 # We use the check unit testing framework for our C unit tests
 # We use the check unit testing framework for our C unit tests
 include(ExternalProject)
 include(ExternalProject)
-if(NOT WIN32)
-  # Apply the patch to check to fix CMake building on OS X
-  set(CHECK_PATCH_COMMAND patch
-     ${CIVETWEB_THIRD_PARTY_DIR}/src/check-unit-test-framework/CMakeLists.txt
-     ${CMAKE_SOURCE_DIR}/cmake/check/c82fe8888aacfe784476112edd3878256d2e30bc.patch
-   )
-else()
-  set(CHECK_PATCH_COMMAND "")
-endif()
+#if(NOT WIN32)
+#  # Apply the patch to check to fix CMake building on OS X
+#  set(CHECK_PATCH_COMMAND patch
+#     ${CIVETWEB_THIRD_PARTY_DIR}/src/check-unit-test-framework/CMakeLists.txt
+#     ${CMAKE_SOURCE_DIR}/cmake/check/c82fe8888aacfe784476112edd3878256d2e30bc.patch
+#   )
+#else()
+#  set(CHECK_PATCH_COMMAND "")
+#endif()
 ExternalProject_Add(check-unit-test-framework
 ExternalProject_Add(check-unit-test-framework
   DEPENDS c-library
   DEPENDS c-library
   URL "https://codeload.github.com/libcheck/check/zip/${CIVETWEB_CHECK_VERSION}"
   URL "https://codeload.github.com/libcheck/check/zip/${CIVETWEB_CHECK_VERSION}"
@@ -161,6 +161,7 @@ civetweb_add_test(PublicFunc "Aux functions")
 
 
 # Public API server tests
 # Public API server tests
 civetweb_add_test(PublicServer "Check test environment")
 civetweb_add_test(PublicServer "Check test environment")
+civetweb_add_test(PublicServer "Init library")
 civetweb_add_test(PublicServer "Start threads")
 civetweb_add_test(PublicServer "Start threads")
 civetweb_add_test(PublicServer "Start Stop HTTP Server")
 civetweb_add_test(PublicServer "Start Stop HTTP Server")
 civetweb_add_test(PublicServer "Start Stop HTTPS Server")
 civetweb_add_test(PublicServer "Start Stop HTTPS Server")
@@ -173,8 +174,9 @@ civetweb_add_test(PublicServer "Error handling")
 civetweb_add_test(PublicServer "Limit speed")
 civetweb_add_test(PublicServer "Limit speed")
 
 
 # Timer tests
 # Timer tests
-civetweb_add_test(Timer "Timer Periodic")
 civetweb_add_test(Timer "Timer Single Shot")
 civetweb_add_test(Timer "Timer Single Shot")
+civetweb_add_test(Timer "Timer Periodic")
+civetweb_add_test(Timer "Timer Mixed")
 
 
 # Tests with main.c
 # Tests with main.c
 #civetweb_add_test(EXE "Helper funcs")
 #civetweb_add_test(EXE "Helper funcs")

+ 1 - 1
test/civetweb_check.h

@@ -1,4 +1,4 @@
-/* Copyright (c) 2015 the Civetweb developers
+/* Copyright (c) 2015-2017 the Civetweb developers
  *
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
  * of this software and associated documentation files (the "Software"), to deal

+ 1 - 1
test/main.c

@@ -1,4 +1,4 @@
-/* Copyright (c) 2015-2016 the Civetweb developers
+/* Copyright (c) 2015-2017 the Civetweb developers
  *
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
  * of this software and associated documentation files (the "Software"), to deal

+ 8 - 0
test/page5.lua

@@ -0,0 +1,8 @@
+mg.write("HTTP/1.0 200 OK\r\n")
+mg.write("Content-Type: text/html\r\n")
+mg.write("\r\n")
+mg.write([[<html><body><p>
+Hello world!
+</p>
+</body></html>
+]])

+ 18 - 18
test/private.c

@@ -1,4 +1,4 @@
-/* Copyright (c) 2015-2016 the Civetweb developers
+/* Copyright (c) 2015-2017 the Civetweb developers
  *
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
  * of this software and associated documentation files (the "Software"), to deal
@@ -346,37 +346,37 @@ END_TEST
 START_TEST(test_mg_vsnprintf)
 START_TEST(test_mg_vsnprintf)
 {
 {
 	char buf[16];
 	char buf[16];
-	int trunc;
+	int is_trunc;
 
 
 	memset(buf, 0, sizeof(buf));
 	memset(buf, 0, sizeof(buf));
 
 
-	trunc = 777;
-	mg_snprintf(NULL, &trunc, buf, 10, "%8i", 123);
+	is_trunc = 777;
+	mg_snprintf(NULL, &is_trunc, buf, 10, "%8i", 123);
 	ck_assert_str_eq(buf, "     123");
 	ck_assert_str_eq(buf, "     123");
-	ck_assert_int_eq(trunc, 0);
+	ck_assert_int_eq(is_trunc, 0);
 
 
-	trunc = 777;
-	mg_snprintf(NULL, &trunc, buf, 10, "%9i", 123);
+	is_trunc = 777;
+	mg_snprintf(NULL, &is_trunc, buf, 10, "%9i", 123);
 	ck_assert_str_eq(buf, "      123");
 	ck_assert_str_eq(buf, "      123");
-	ck_assert_int_eq(trunc, 0);
+	ck_assert_int_eq(is_trunc, 0);
 
 
-	trunc = 777;
-	mg_snprintf(NULL, &trunc, buf, 9, "%9i", 123);
+	is_trunc = 777;
+	mg_snprintf(NULL, &is_trunc, buf, 9, "%9i", 123);
 	ck_assert_str_eq(buf, "      12");
 	ck_assert_str_eq(buf, "      12");
-	ck_assert_int_eq(trunc, 1);
+	ck_assert_int_eq(is_trunc, 1);
 
 
-	trunc = 777;
-	mg_snprintf(NULL, &trunc, buf, 8, "%9i", 123);
+	is_trunc = 777;
+	mg_snprintf(NULL, &is_trunc, buf, 8, "%9i", 123);
 	ck_assert_str_eq(buf, "      1");
 	ck_assert_str_eq(buf, "      1");
-	ck_assert_int_eq(trunc, 1);
+	ck_assert_int_eq(is_trunc, 1);
 
 
-	trunc = 777;
-	mg_snprintf(NULL, &trunc, buf, 7, "%9i", 123);
+	is_trunc = 777;
+	mg_snprintf(NULL, &is_trunc, buf, 7, "%9i", 123);
 	ck_assert_str_eq(buf, "      ");
 	ck_assert_str_eq(buf, "      ");
-	ck_assert_int_eq(trunc, 1);
+	ck_assert_int_eq(is_trunc, 1);
 
 
 	strcpy(buf, "1234567890");
 	strcpy(buf, "1234567890");
-	mg_snprintf(NULL, &trunc, buf, 0, "%i", 543);
+	mg_snprintf(NULL, &is_trunc, buf, 0, "%i", 543);
 	ck_assert_str_eq(buf, "1234567890");
 	ck_assert_str_eq(buf, "1234567890");
 }
 }
 END_TEST
 END_TEST

+ 1 - 1
test/private.h

@@ -1,4 +1,4 @@
-/* Copyright (c) 2015 the Civetweb developers
+/* Copyright (c) 2015-2017 the Civetweb developers
  *
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
  * of this software and associated documentation files (the "Software"), to deal

+ 1 - 1
test/private_exe.c

@@ -1,4 +1,4 @@
-/* Copyright (c) 2015-2016 the Civetweb developers
+/* Copyright (c) 2015-2017 the Civetweb developers
  *
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
  * of this software and associated documentation files (the "Software"), to deal

+ 1 - 1
test/private_exe.h

@@ -1,4 +1,4 @@
-/* Copyright (c) 2015 the Civetweb developers
+/* Copyright (c) 2015-2017 the Civetweb developers
  *
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
  * of this software and associated documentation files (the "Software"), to deal

+ 1 - 1
test/public_func.c

@@ -1,4 +1,4 @@
-/* Copyright (c) 2015-2016 the Civetweb developers
+/* Copyright (c) 2015-2017 the Civetweb developers
  *
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
  * of this software and associated documentation files (the "Software"), to deal

+ 1 - 1
test/public_func.h

@@ -1,4 +1,4 @@
-/* Copyright (c) 2015 the Civetweb developers
+/* Copyright (c) 2015-2017 the Civetweb developers
  *
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
  * of this software and associated documentation files (the "Software"), to deal

+ 32 - 3
test/public_server.c

@@ -1,4 +1,4 @@
-/* Copyright (c) 2015-2016 the Civetweb developers
+/* Copyright (c) 2015-2017 the Civetweb developers
  *
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
  * of this software and associated documentation files (the "Software"), to deal
@@ -3531,8 +3531,17 @@ START_TEST(test_throttle)
 	dt = difftime(t1, t0) * 1000.0; /* Elapsed time in ms - in most systems
 	dt = difftime(t1, t0) * 1000.0; /* Elapsed time in ms - in most systems
 	                                 * only with second resolution */
 	                                 * only with second resolution */
 
 
-	/* Check if there are at least 10 seconds */
-	ck_assert_int_ge((int)dt, 10 * 1000);
+	/* Time estimation: Data size is 10 kB, with 1 kB/s speed limit.
+	 * The first block (1st kB) is transferred immediately, the second
+	 * block (2nd kB) one second later, the third block (3rd kB) two
+	 * seconds later, .. the last block (10th kB) nine seconds later.
+	 * The resolution of time measurement using the "time" C library
+	 * function is 1 second, so we should add +/- one second tolerance.
+	 * Thus, download of 10 kB with 1 kB/s should not be faster than
+	 * 8 seconds. */
+
+	/* Check if there are at least 8 seconds */
+	ck_assert_int_ge((int)dt, 8 * 1000);
 
 
 	/* Nothing left to read */
 	/* Nothing left to read */
 	r = mg_read(client, client_data_buf, sizeof(client_data_buf));
 	r = mg_read(client, client_data_buf, sizeof(client_data_buf));
@@ -3547,12 +3556,22 @@ START_TEST(test_throttle)
 END_TEST
 END_TEST
 
 
 
 
+START_TEST(test_init_library)
+{
+	unsigned f_avail = mg_check_feature(0xFF);
+	unsigned f_ret = mg_init_library(f_avail);
+	ck_assert_uint_eq(f_ret, f_avail);
+}
+END_TEST
+
+
 Suite *
 Suite *
 make_public_server_suite(void)
 make_public_server_suite(void)
 {
 {
 	Suite *const suite = suite_create("PublicServer");
 	Suite *const suite = suite_create("PublicServer");
 
 
 	TCase *const tcase_checktestenv = tcase_create("Check test environment");
 	TCase *const tcase_checktestenv = tcase_create("Check test environment");
+	TCase *const tcase_initlib = tcase_create("Init library");
 	TCase *const tcase_startthreads = tcase_create("Start threads");
 	TCase *const tcase_startthreads = tcase_create("Start threads");
 	TCase *const tcase_startstophttp = tcase_create("Start Stop HTTP Server");
 	TCase *const tcase_startstophttp = tcase_create("Start Stop HTTP Server");
 	TCase *const tcase_startstophttps = tcase_create("Start Stop HTTPS Server");
 	TCase *const tcase_startstophttps = tcase_create("Start Stop HTTPS Server");
@@ -3569,6 +3588,10 @@ make_public_server_suite(void)
 	tcase_set_timeout(tcase_checktestenv, civetweb_min_test_timeout);
 	tcase_set_timeout(tcase_checktestenv, civetweb_min_test_timeout);
 	suite_add_tcase(suite, tcase_checktestenv);
 	suite_add_tcase(suite, tcase_checktestenv);
 
 
+	tcase_add_test(tcase_initlib, test_init_library);
+	tcase_set_timeout(tcase_initlib, civetweb_min_test_timeout);
+	suite_add_tcase(suite, tcase_initlib);
+
 	tcase_add_test(tcase_startthreads, test_threading);
 	tcase_add_test(tcase_startthreads, test_threading);
 	tcase_set_timeout(tcase_startthreads, civetweb_min_test_timeout);
 	tcase_set_timeout(tcase_startthreads, civetweb_min_test_timeout);
 	suite_add_tcase(suite, tcase_startthreads);
 	suite_add_tcase(suite, tcase_startthreads);
@@ -3624,6 +3647,10 @@ static int chk_failed = 0;
 void
 void
 MAIN_PUBLIC_SERVER(void)
 MAIN_PUBLIC_SERVER(void)
 {
 {
+	unsigned f_avail = mg_check_feature(0xFF);
+	unsigned f_ret = mg_init_library(f_avail);
+	ck_assert_uint_eq(f_ret, f_avail);
+
 	test_the_test_environment(0);
 	test_the_test_environment(0);
 	test_threading(0);
 	test_threading(0);
 
 
@@ -3638,6 +3665,8 @@ MAIN_PUBLIC_SERVER(void)
 	test_error_log_file(0);
 	test_error_log_file(0);
 	test_throttle(0);
 	test_throttle(0);
 
 
+	mg_exit_library();
+
 	printf("\nok: %i\nfailed: %i\n\n", chk_ok, chk_failed);
 	printf("\nok: %i\nfailed: %i\n\n", chk_ok, chk_failed);
 }
 }
 
 

+ 1 - 1
test/public_server.h

@@ -1,4 +1,4 @@
-/* Copyright (c) 2015 the Civetweb developers
+/* Copyright (c) 2015-2017 the Civetweb developers
  *
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
  * of this software and associated documentation files (the "Software"), to deal

+ 1 - 1
test/shared.c

@@ -1,4 +1,4 @@
-/* Copyright (c) 2015-2016 the Civetweb developers
+/* Copyright (c) 2015-2017 the Civetweb developers
  *
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
  * of this software and associated documentation files (the "Software"), to deal

+ 1 - 1
test/shared.h

@@ -1,4 +1,4 @@
-/* Copyright (c) 2015 the Civetweb developers
+/* Copyright (c) 2015-2017 the Civetweb developers
  *
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
  * of this software and associated documentation files (the "Software"), to deal

+ 182 - 45
test/timertest.c

@@ -1,4 +1,4 @@
-/* Copyright (c) 2016 the Civetweb developers
+/* Copyright (c) 2016-2017 the Civetweb developers
  *
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
  * of this software and associated documentation files (the "Software"), to deal
@@ -44,16 +44,35 @@
 
 
 #include "timertest.h"
 #include "timertest.h"
 
 
+static int action_dec_ret;
 
 
 static int
 static int
-action1(void *arg)
+action_dec(void *arg)
 {
 {
 	int *p = (int *)arg;
 	int *p = (int *)arg;
 	(*p)--;
 	(*p)--;
 
 
-	ck_assert_int_ge(*p, -1);
+	if (*p < -1) {
+		ck_abort_msg("Periodic timer called too often");
+		/* return 0 here would be unreachable code */
+	}
 
 
-	return 1;
+	return (*p >= -3) ? action_dec_ret : 0;
+}
+
+
+static int
+action_dec_to_0(void *arg)
+{
+	int *p = (int *)arg;
+	(*p)--;
+
+	if (*p <= -1) {
+		ck_abort_msg("Periodic timer called too often");
+		/* return 0 here would be unreachable code */
+	}
+
+	return (*p > 0);
 }
 }
 
 
 
 
@@ -64,103 +83,132 @@ START_TEST(test_timer_cyclic)
 	memset(&ctx, 0, sizeof(ctx));
 	memset(&ctx, 0, sizeof(ctx));
 	memset(c, 0, sizeof(c));
 	memset(c, 0, sizeof(c));
 
 
+	action_dec_ret = 1;
+
 	mark_point();
 	mark_point();
 	timers_init(&ctx);
 	timers_init(&ctx);
 	mg_sleep(100);
 	mg_sleep(100);
 	mark_point();
 	mark_point();
 
 
-	c[0] = 10;
-	timer_add(&ctx, 0, 0.1, 1, action1, c + 0);
-	c[2] = 2;
-	timer_add(&ctx, 0, 0.5, 1, action1, c + 2);
-	c[1] = 5;
-	timer_add(&ctx, 0, 0.2, 1, action1, c + 1);
+	c[0] = 100;
+	timer_add(&ctx, 0.05, 0.1, 1, action_dec, c + 0);
+	c[2] = 20;
+	timer_add(&ctx, 0.25, 0.5, 1, action_dec, c + 2);
+	c[1] = 50;
+	timer_add(&ctx, 0.1, 0.2, 1, action_dec, c + 1);
 
 
-    mark_point();
+	mark_point();
 
 
-	mg_sleep(1000); /* Sleep 1 second - timer will run */
+	mg_sleep(10000); /* Sleep 10 second - timers will run */
 
 
-    mark_point();
+	mark_point();
 	ctx.stop_flag = 99; /* End timer thread */
 	ctx.stop_flag = 99; /* End timer thread */
-    mark_point();
+	mark_point();
 
 
-	mg_sleep(1000); /* Sleep 1 second - timer will not run */
+	mg_sleep(2000); /* Sleep 2 second - timers will not run */
 
 
-    mark_point();
+	mark_point();
 
 
 	timers_exit(&ctx);
 	timers_exit(&ctx);
 
 
 	mark_point();
 	mark_point();
-    mg_sleep(100);
 
 
-#ifdef LOCAL_TEST
-    /* If performed locally (on a physical machine that's not overloaded),
-     * timing will be precise to the ~100 ms required here. */
-	ck_assert_int_eq(c[0], 0);
-	ck_assert_int_eq(c[1], 0);
-	ck_assert_int_eq(c[2], 0);
-#else
-    /* If this test runs in a virtual environment, like the CI unit test
-     * containers, there might be some timing deviations, so check the
-     * counter with some tolerance. */
+/* If this test runs in a virtual environment, like the CI unit test
+ * containers, there might be some timing deviations, so check the
+ * counter with some tolerance. */
+
 	ck_assert_int_ge(c[0], -1);
 	ck_assert_int_ge(c[0], -1);
 	ck_assert_int_le(c[0], +1);
 	ck_assert_int_le(c[0], +1);
 	ck_assert_int_ge(c[1], -1);
 	ck_assert_int_ge(c[1], -1);
 	ck_assert_int_le(c[1], +1);
 	ck_assert_int_le(c[1], +1);
 	ck_assert_int_ge(c[2], -1);
 	ck_assert_int_ge(c[2], -1);
 	ck_assert_int_le(c[2], +1);
 	ck_assert_int_le(c[2], +1);
-#endif
 }
 }
 END_TEST
 END_TEST
 
 
 
 
-static int
-action2(void *arg)
+START_TEST(test_timer_oneshot_by_callback_retval)
 {
 {
-	int *p = (int *)arg;
-	(*p)--;
+	struct mg_context ctx;
+	int c[10];
+	memset(&ctx, 0, sizeof(ctx));
+	memset(c, 0, sizeof(c));
+
+	action_dec_ret = 0;
+
+	mark_point();
+	timers_init(&ctx);
+	mg_sleep(100);
+	mark_point();
 
 
-	ck_assert_int_ge(*p, -1);
+	c[0] = 10;
+	timer_add(&ctx, 0, 0.1, 1, action_dec, c + 0);
+	c[2] = 2;
+	timer_add(&ctx, 0, 0.5, 1, action_dec, c + 2);
+	c[1] = 5;
+	timer_add(&ctx, 0, 0.2, 1, action_dec, c + 1);
+
+	mark_point();
+
+	mg_sleep(1000); /* Sleep 1 second - timer will run */
+
+	mark_point();
+	ctx.stop_flag = 99; /* End timer thread */
+	mark_point();
+
+	mg_sleep(1000); /* Sleep 1 second - timer will not run */
+
+	mark_point();
+
+	timers_exit(&ctx);
+
+	mark_point();
+	mg_sleep(100);
 
 
-	return 0;
+	ck_assert_int_eq(c[0], 9);
+	ck_assert_int_eq(c[1], 4);
+	ck_assert_int_eq(c[2], 1);
 }
 }
+END_TEST
 
 
 
 
-START_TEST(test_timer_oneshot)
+START_TEST(test_timer_oneshot_by_timer_add)
 {
 {
 	struct mg_context ctx;
 	struct mg_context ctx;
 	int c[10];
 	int c[10];
 	memset(&ctx, 0, sizeof(ctx));
 	memset(&ctx, 0, sizeof(ctx));
 	memset(c, 0, sizeof(c));
 	memset(c, 0, sizeof(c));
 
 
+	action_dec_ret = 1;
+
 	mark_point();
 	mark_point();
 	timers_init(&ctx);
 	timers_init(&ctx);
 	mg_sleep(100);
 	mg_sleep(100);
 	mark_point();
 	mark_point();
 
 
 	c[0] = 10;
 	c[0] = 10;
-	timer_add(&ctx, 0, 0.1, 1, action2, c + 0);
+	timer_add(&ctx, 0, 0, 1, action_dec, c + 0);
 	c[2] = 2;
 	c[2] = 2;
-	timer_add(&ctx, 0, 0.5, 1, action2, c + 2);
+	timer_add(&ctx, 0, 0, 1, action_dec, c + 2);
 	c[1] = 5;
 	c[1] = 5;
-	timer_add(&ctx, 0, 0.2, 1, action2, c + 1);
+	timer_add(&ctx, 0, 0, 1, action_dec, c + 1);
 
 
-    mark_point();
+	mark_point();
 
 
 	mg_sleep(1000); /* Sleep 1 second - timer will run */
 	mg_sleep(1000); /* Sleep 1 second - timer will run */
 
 
-    mark_point();
+	mark_point();
 	ctx.stop_flag = 99; /* End timer thread */
 	ctx.stop_flag = 99; /* End timer thread */
-    mark_point();
+	mark_point();
 
 
 	mg_sleep(1000); /* Sleep 1 second - timer will not run */
 	mg_sleep(1000); /* Sleep 1 second - timer will not run */
 
 
-    mark_point();
+	mark_point();
 
 
 	timers_exit(&ctx);
 	timers_exit(&ctx);
 
 
 	mark_point();
 	mark_point();
-    mg_sleep(100);
+	mg_sleep(100);
 
 
 	ck_assert_int_eq(c[0], 9);
 	ck_assert_int_eq(c[0], 9);
 	ck_assert_int_eq(c[1], 4);
 	ck_assert_int_eq(c[1], 4);
@@ -169,6 +217,78 @@ START_TEST(test_timer_oneshot)
 END_TEST
 END_TEST
 
 
 
 
+START_TEST(test_timer_mixed)
+{
+	struct mg_context ctx;
+	int c[10];
+	memset(&ctx, 0, sizeof(ctx));
+	memset(c, 0, sizeof(c));
+
+	mark_point();
+	timers_init(&ctx);
+	mg_sleep(100);
+	mark_point();
+
+	/* 3 --> 2, because it is a single shot timer */
+	c[0] = 3;
+	timer_add(&ctx, 0, 0, 1, action_dec_to_0, &c[0]);
+
+	/* 3 --> 0, because it will run until c[1] = 0 and then stop */
+	c[1] = 3;
+	timer_add(&ctx, 0, 0.2, 1, action_dec_to_0, &c[1]);
+
+	/* 3 --> 1, with 750 ms period, it will run once at start,
+	 * then once 750 ms later, but not 1500 ms later, since the
+	 * timer is already stopped then. */
+	c[2] = 3;
+	timer_add(&ctx, 0, 0.75, 1, action_dec_to_0, &c[2]);
+
+	/* 3 --> 2, will run at start, but no cyclic in 1 second */
+	c[3] = 3;
+	timer_add(&ctx, 0, 2.5, 1, action_dec_to_0, &c[3]);
+
+	/* 3 --> 3, will not run at start */
+	c[4] = 3;
+	timer_add(&ctx, 2.5, 0.1, 1, action_dec_to_0, &c[4]);
+
+	/* 3 --> 2, an absolute timer in the past (-123.456) will still
+	 * run once at start, and then with the period */
+	c[5] = 3;
+	timer_add(&ctx, -123.456, 2.5, 0, action_dec_to_0, &c[5]);
+
+	/* 3 --> 1, an absolute timer in the past (-123.456) will still
+	 * run once at start, and then with the period */
+	c[6] = 3;
+	timer_add(&ctx, -123.456, 0.75, 0, action_dec_to_0, &c[6]);
+
+	mark_point();
+
+	mg_sleep(1000); /* Sleep 1 second - timer will run */
+
+	mark_point();
+	ctx.stop_flag = 99; /* End timer thread */
+	mark_point();
+
+	mg_sleep(1000); /* Sleep 1 second - timer will not run */
+
+	mark_point();
+
+	timers_exit(&ctx);
+
+	mark_point();
+	mg_sleep(100);
+
+	ck_assert_int_eq(c[0], 2);
+	ck_assert_int_eq(c[1], 0);
+	ck_assert_int_eq(c[2], 1);
+	ck_assert_int_eq(c[3], 2);
+	ck_assert_int_eq(c[4], 3);
+	ck_assert_int_eq(c[5], 2);
+	ck_assert_int_eq(c[6], 1);
+}
+END_TEST
+
+
 Suite *
 Suite *
 make_timertest_suite(void)
 make_timertest_suite(void)
 {
 {
@@ -176,15 +296,21 @@ make_timertest_suite(void)
 
 
 	TCase *const tcase_timer_cyclic = tcase_create("Timer Periodic");
 	TCase *const tcase_timer_cyclic = tcase_create("Timer Periodic");
 	TCase *const tcase_timer_oneshot = tcase_create("Timer Single Shot");
 	TCase *const tcase_timer_oneshot = tcase_create("Timer Single Shot");
+	TCase *const tcase_timer_mixed = tcase_create("Timer Mixed");
 
 
 	tcase_add_test(tcase_timer_cyclic, test_timer_cyclic);
 	tcase_add_test(tcase_timer_cyclic, test_timer_cyclic);
 	tcase_set_timeout(tcase_timer_cyclic, 30);
 	tcase_set_timeout(tcase_timer_cyclic, 30);
 	suite_add_tcase(suite, tcase_timer_cyclic);
 	suite_add_tcase(suite, tcase_timer_cyclic);
 
 
-	tcase_add_test(tcase_timer_oneshot, test_timer_oneshot);
+	tcase_add_test(tcase_timer_oneshot, test_timer_oneshot_by_timer_add);
+	tcase_add_test(tcase_timer_oneshot, test_timer_oneshot_by_callback_retval);
 	tcase_set_timeout(tcase_timer_oneshot, 30);
 	tcase_set_timeout(tcase_timer_oneshot, 30);
 	suite_add_tcase(suite, tcase_timer_oneshot);
 	suite_add_tcase(suite, tcase_timer_oneshot);
 
 
+	tcase_add_test(tcase_timer_mixed, test_timer_mixed);
+	tcase_set_timeout(tcase_timer_mixed, 30);
+	suite_add_tcase(suite, tcase_timer_mixed);
+
 	return suite;
 	return suite;
 }
 }
 
 
@@ -195,13 +321,24 @@ make_timertest_suite(void)
 void
 void
 TIMER_PRIVATE(void)
 TIMER_PRIVATE(void)
 {
 {
+	unsigned f_avail;
+	unsigned f_ret;
+
 #if defined(_WIN32)
 #if defined(_WIN32)
 	WSADATA data;
 	WSADATA data;
 	WSAStartup(MAKEWORD(2, 2), &data);
 	WSAStartup(MAKEWORD(2, 2), &data);
 #endif
 #endif
 
 
+	f_avail = mg_check_feature(0xFF);
+	f_ret = mg_init_library(f_avail);
+	ck_assert_uint_eq(f_ret, f_avail);
+
 	test_timer_cyclic(0);
 	test_timer_cyclic(0);
-	test_timer_oneshot(0);
+	test_timer_oneshot_by_timer_add(0);
+	test_timer_oneshot_by_callback_retval(0);
+	test_timer_mixed(0);
+
+	mg_exit_library();
 
 
 #if defined(_WIN32)
 #if defined(_WIN32)
 	WSACleanup();
 	WSACleanup();

+ 0 - 1549
test/unit_test.c

@@ -1,1549 +0,0 @@
-/* Copyright (c) 2013-2015 the Civetweb developers
- * Copyright (c) 2004-2013 Sergey Lyubka
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
-#if defined(_MSC_VER)
-#pragma message("Warning: " __FILE__ " is obsolete. See note below.")
-#else
-#pragma message __FILE__ ": This file is obsolete. See note below."
-#endif
-
-/* Note: The unit_test.c file is mostly obsolete, since the current unit
- * tests are performed by the CMake build framework based on the
- * CMakeList (https://github.com/civetweb/civetweb/blob/master/test/CMakeLists.txt).
- * The tests do no longer use unit_test.c but public_func.c, public_server.c,
- * private.c, private_exe.c and others.
- * This file is left here for reference and will be removed in the future.
- * It is no longer actively maintained.
- */
-
-
-/* Unit test for the civetweb web server. Tests embedded API.
- */
-#define USE_WEBSOCKET
-
-#ifndef _WIN32
-#define __cdecl
-#define USE_IPV6
-#endif
-
-/* USE_* definitions must be made before #include "civetweb.c" !
- * We include the source file so that our object file will have visibility to
- * all the static functions.
- */
-
-#include "civetweb.c"
-
-void check_func(int condition, const char *cond_txt, unsigned line);
-
-static int s_total_tests = 0;
-static int s_failed_tests = 0;
-
-void check_func(int condition, const char *cond_txt, unsigned line)
-{
-	++s_total_tests;
-	if (!condition) {
-		printf("Fail on line %d: [%s]\n", line, cond_txt);
-		++s_failed_tests;
-	}
-}
-
-#define ASSERT(expr)                                                           \
-	do {                                                                       \
-		check_func(expr, #expr, __LINE__);                                     \
-	} while (0)
-
-#define REQUIRE(expr)                                                          \
-	do {                                                                       \
-		check_func(expr, #expr, __LINE__);                                     \
-		if (!(expr)) {                                                         \
-			exit(EXIT_FAILURE);                                                \
-		}                                                                      \
-	} while (0)
-
-#define HTTP_PORT "8080"
-#ifdef NO_SSL
-#define HTTPS_PORT HTTP_PORT
-#define LISTENING_ADDR "127.0.0.1:" HTTP_PORT
-#else
-#define HTTP_REDIRECT_PORT "8088"
-#define HTTPS_PORT "8443"
-#define LISTENING_ADDR                                                         \
-	"127.0.0.1:" HTTP_PORT ",127.0.0.1:" HTTP_REDIRECT_PORT "r"                \
-	",127.0.0.1:" HTTPS_PORT "s"
-#endif
-
-static void test_parse_http_message()
-{
-	struct mg_request_info ri;
-	char req1[] = "GET / HTTP/1.1\r\n\r\n";
-	char req2[] = "BLAH / HTTP/1.1\r\n\r\n";
-	char req3[] = "GET / HTTP/1.1\r\nBah\r\n";
-	char req4[] = "GET / HTTP/1.1\r\nA: foo bar\r\nB: bar\r\nbaz:\r\n\r\n";
-	char req5[] = "GET / HTTP/1.1\r\n\r\n";
-	char req6[] = "G";
-	char req7[] = " blah ";
-	char req8[] = " HTTP/1.1 200 OK \n\n";
-	char req9[] = "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n";
-
-	ASSERT(parse_http_message(req9, sizeof(req9), &ri) == sizeof(req9) - 1);
-	ASSERT(ri.num_headers == 1);
-
-	ASSERT(parse_http_message(req1, sizeof(req1), &ri) == sizeof(req1) - 1);
-	ASSERT(strcmp(ri.http_version, "1.1") == 0);
-	ASSERT(ri.num_headers == 0);
-
-	ASSERT(parse_http_message(req2, sizeof(req2), &ri) == -1);
-	ASSERT(parse_http_message(req3, sizeof(req3), &ri) == 0);
-	ASSERT(parse_http_message(req6, sizeof(req6), &ri) == 0);
-	ASSERT(parse_http_message(req7, sizeof(req7), &ri) == 0);
-	ASSERT(parse_http_message("", 0, &ri) == 0);
-	ASSERT(parse_http_message(req8, sizeof(req8), &ri) == sizeof(req8) - 1);
-
-	/* TODO(lsm): Fix this. Header value may span multiple lines. */
-	ASSERT(parse_http_message(req4, sizeof(req4), &ri) == sizeof(req4) - 1);
-	ASSERT(strcmp(ri.http_version, "1.1") == 0);
-	ASSERT(ri.num_headers == 3);
-	ASSERT(strcmp(ri.http_headers[0].name, "A") == 0);
-	ASSERT(strcmp(ri.http_headers[0].value, "foo bar") == 0);
-	ASSERT(strcmp(ri.http_headers[1].name, "B") == 0);
-	ASSERT(strcmp(ri.http_headers[1].value, "bar") == 0);
-	ASSERT(strcmp(ri.http_headers[2].name, "baz") == 0);
-	ASSERT(strcmp(ri.http_headers[2].value, "") == 0);
-
-	ASSERT(parse_http_message(req5, sizeof(req5), &ri) == sizeof(req5) - 1);
-	ASSERT(strcmp(ri.request_method, "GET") == 0);
-	ASSERT(strcmp(ri.http_version, "1.1") == 0);
-}
-
-static void test_should_keep_alive(void)
-{
-	struct mg_connection conn;
-	struct mg_context ctx;
-	char req1[] = "GET / HTTP/1.1\r\n\r\n";
-	char req2[] = "GET / HTTP/1.0\r\n\r\n";
-	char req3[] = "GET / HTTP/1.1\r\nConnection: close\r\n\r\n";
-	char req4[] = "GET / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n";
-
-	memset(&conn, 0, sizeof(conn));
-	conn.ctx = &ctx;
-	ASSERT(parse_http_message(req1, sizeof(req1), &conn.request_info) ==
-	       sizeof(req1) - 1);
-
-	ctx.config[ENABLE_KEEP_ALIVE] = "no";
-	ASSERT(should_keep_alive(&conn) == 0);
-
-	ctx.config[ENABLE_KEEP_ALIVE] = "yes";
-	ASSERT(should_keep_alive(&conn) == 1);
-
-	conn.must_close = 1;
-	ASSERT(should_keep_alive(&conn) == 0);
-
-	conn.must_close = 0;
-	parse_http_message(req2, sizeof(req2), &conn.request_info);
-	ASSERT(should_keep_alive(&conn) == 0);
-
-	parse_http_message(req3, sizeof(req3), &conn.request_info);
-	ASSERT(should_keep_alive(&conn) == 0);
-
-	parse_http_message(req4, sizeof(req4), &conn.request_info);
-	ASSERT(should_keep_alive(&conn) == 1);
-
-	conn.status_code = 401;
-	ASSERT(should_keep_alive(&conn) == 0);
-
-	conn.status_code = 200;
-	conn.must_close = 1;
-	ASSERT(should_keep_alive(&conn) == 0);
-}
-
-static void test_match_prefix(void)
-{
-	ASSERT(match_prefix("/api", 4, "/api") == 4);
-	ASSERT(match_prefix("/a/", 3, "/a/b/c") == 3);
-	ASSERT(match_prefix("/a/", 3, "/ab/c") == -1);
-	ASSERT(match_prefix("/*/", 3, "/ab/c") == 4);
-	ASSERT(match_prefix("**", 2, "/a/b/c") == 6);
-	ASSERT(match_prefix("/*", 2, "/a/b/c") == 2);
-	ASSERT(match_prefix("*/*", 3, "/a/b/c") == 2);
-	ASSERT(match_prefix("**/", 3, "/a/b/c") == 5);
-	ASSERT(match_prefix("**.foo|**.bar", 13, "a.bar") == 5);
-	ASSERT(match_prefix("a|b|cd", 6, "cdef") == 2);
-	ASSERT(match_prefix("a|b|c?", 6, "cdef") == 2);
-	ASSERT(match_prefix("a|?|cd", 6, "cdef") == 1);
-	ASSERT(match_prefix("/a/**.cgi", 9, "/foo/bar/x.cgi") == -1);
-	ASSERT(match_prefix("/a/**.cgi", 9, "/a/bar/x.cgi") == 12);
-	ASSERT(match_prefix("**/", 3, "/a/b/c") == 5);
-	ASSERT(match_prefix("**/$", 4, "/a/b/c") == -1);
-	ASSERT(match_prefix("**/$", 4, "/a/b/") == 5);
-	ASSERT(match_prefix("$", 1, "") == 0);
-	ASSERT(match_prefix("$", 1, "x") == -1);
-	ASSERT(match_prefix("*$", 2, "x") == 1);
-	ASSERT(match_prefix("/$", 2, "/") == 1);
-	ASSERT(match_prefix("**/$", 4, "/a/b/c") == -1);
-	ASSERT(match_prefix("**/$", 4, "/a/b/") == 5);
-	ASSERT(match_prefix("*", 1, "/hello/") == 0);
-	ASSERT(match_prefix("**.a$|**.b$", 11, "/a/b.b/") == -1);
-	ASSERT(match_prefix("**.a$|**.b$", 11, "/a/b.b") == 6);
-	ASSERT(match_prefix("**.a$|**.b$", 11, "/a/B.A") == 6);
-	ASSERT(match_prefix("**o$", 4, "HELLO") == 5);
-}
-
-static void test_remove_double_dots()
-{
-	struct {
-		char before[20], after[20];
-	} data[] = {
-	    {"////a", "/a"},
-	    {"/.....", "/."},
-	    {"/......", "/"},
-	    {"...", "..."},
-	    {"/...///", "/./"},
-	    {"/a...///", "/a.../"},
-	    {"/.x", "/.x"},
-	    {"/\\", "/"},
-	    {"/a\\", "/a\\"},
-	    {"/a\\\\...", "/a\\."},
-	};
-	size_t i;
-
-	for (i = 0; i < ARRAY_SIZE(data); i++) {
-		remove_double_dots_and_double_slashes(data[i].before);
-		ASSERT(strcmp(data[i].before, data[i].after) == 0);
-	}
-}
-
-static char *read_file(const char *path, int *size)
-{
-	FILE *fp;
-	struct stat st;
-	char *data = NULL;
-	if ((fp = fopen(path, "rb")) != NULL && !fstat(fileno(fp), &st)) {
-		*size = (int)st.st_size;
-		data = mg_malloc(*size);
-		ASSERT(data != NULL);
-		ASSERT(fread(data, 1, *size, fp) == (size_t)*size);
-		fclose(fp);
-	}
-	return data;
-}
-
-static long fetch_data_size = 1024 * 1024;
-static char *fetch_data;
-static const char *inmemory_file_data = "hi there";
-static const char *upload_filename = "upload_test.txt";
-#if 0
-static const char *upload_filename2 = "upload_test2.txt";
-#endif
-static const char *upload_ok_message = "upload successful";
-
-static const char *
-open_file_cb(const struct mg_connection *conn, const char *path, size_t *size)
-{
-	(void)conn;
-	if (!strcmp(path, "./blah")) {
-		*size = strlen(inmemory_file_data);
-		return inmemory_file_data;
-	}
-	return NULL;
-}
-
-#if defined(MG_LEGACY_INTERFACE)
-static void upload_cb(struct mg_connection *conn, const char *path)
-{
-	const struct mg_request_info *ri = mg_get_request_info(conn);
-	char *p1, *p2;
-	int len1, len2;
-
-	if (atoi(ri->query_string) == 1) {
-		ASSERT(!strcmp(path, "./upload_test.txt"));
-		ASSERT((p1 = read_file("src/civetweb.c", &len1)) != NULL);
-		ASSERT((p2 = read_file(path, &len2)) != NULL);
-		ASSERT(len1 == len2);
-		ASSERT(memcmp(p1, p2, len1) == 0);
-		mg_free(p1);
-		mg_free(p2);
-		remove(upload_filename);
-	} else if (atoi(ri->query_string) == 2) {
-		if (!strcmp(path, "./upload_test.txt")) {
-			ASSERT((p1 = read_file("include/civetweb.h", &len1)) != NULL);
-			ASSERT((p2 = read_file(path, &len2)) != NULL);
-			ASSERT(len1 == len2);
-			ASSERT(memcmp(p1, p2, len1) == 0);
-			mg_free(p1);
-			mg_free(p2);
-			remove(upload_filename);
-		} else if (!strcmp(path, "./upload_test2.txt")) {
-			ASSERT((p1 = read_file("README.md", &len1)) != NULL);
-			ASSERT((p2 = read_file(path, &len2)) != NULL);
-			ASSERT(len1 == len2);
-			ASSERT(memcmp(p1, p2, len1) == 0);
-			mg_free(p1);
-			mg_free(p2);
-			remove(upload_filename);
-		} else {
-			ASSERT(0);
-		}
-	} else {
-		ASSERT(0);
-	}
-
-	mg_printf(conn,
-	          "HTTP/1.0 200 OK\r\nContent-Length: %d\r\n\r\n%s",
-	          (int)strlen(upload_ok_message),
-	          upload_ok_message);
-}
-#endif
-
-static int begin_request_handler_cb(struct mg_connection *conn)
-{
-
-	const struct mg_request_info *ri = mg_get_request_info(conn);
-	int req_len = (int)(ri->content_length);
-	const char *s_req_len = mg_get_header(conn, "Content-Length");
-	char *data;
-	long to_write, write_now;
-	int bytes_read, bytes_written;
-
-	ASSERT(((req_len == -1) && (s_req_len == NULL)) ||
-	       ((s_req_len != NULL) && (req_len = atol(s_req_len))));
-
-	if (!strncmp(ri->uri, "/data/", 6)) {
-		if (!strcmp(ri->uri + 6, "all")) {
-			to_write = fetch_data_size;
-		} else {
-			to_write = atol(ri->uri + 6);
-		}
-		mg_printf(conn,
-		          "HTTP/1.1 200 OK\r\n"
-		          "Connection: close\r\n"
-		          "Content-Length: %li\r\n"
-		          "Content-Type: text/plain\r\n\r\n",
-		          to_write);
-		while (to_write > 0) {
-			write_now = to_write > fetch_data_size ? fetch_data_size : to_write;
-			bytes_written = mg_write(conn, fetch_data, write_now);
-			ASSERT(bytes_written == write_now);
-			if (bytes_written < 0) {
-				ASSERT(0);
-				break;
-			}
-			to_write -= bytes_written;
-		}
-		close_connection(conn);
-		return 1;
-	}
-
-	if (!strcmp(ri->uri, "/content_length")) {
-		if (req_len > 0) {
-			data = mg_malloc(req_len);
-			assert(data != NULL);
-			bytes_read = mg_read(conn, data, req_len);
-			ASSERT(bytes_read == req_len);
-
-			mg_printf(conn,
-			          "HTTP/1.1 200 OK\r\n"
-			          "Connection: close\r\n"
-			          "Content-Length: %d\r\n" /* The official definition */
-			          "Content-Type: text/plain\r\n\r\n",
-			          bytes_read);
-			mg_write(conn, data, bytes_read);
-
-			mg_free(data);
-		} else {
-			data = mg_malloc(1024);
-			assert(data != NULL);
-			bytes_read = mg_read(conn, data, 1024);
-
-			mg_printf(conn,
-			          "HTTP/1.1 200 OK\r\n"
-			          "Connection: close\r\n"
-			          "Content-Type: text/plain\r\n\r\n");
-			mg_write(conn, data, bytes_read);
-
-			mg_free(data);
-		}
-		close_connection(conn);
-		return 1;
-	}
-
-#if defined(MG_LEGACY_INTERFACE)
-	if (!strcmp(ri->uri, "/upload")) {
-		ASSERT(ri->query_string != NULL);
-		ASSERT(mg_upload(conn, ".") == atoi(ri->query_string));
-	}
-#endif
-
-	return 0;
-}
-
-static int log_message_cb(const struct mg_connection *conn, const char *msg)
-{
-	(void)conn;
-	printf("%s\n", msg);
-	return 0;
-}
-
-
-int (*begin_request)(struct mg_connection *);
-void (*end_request)(const struct mg_connection *, int reply_status_code);
-int (*log_message)(const struct mg_connection *, const char *message);
-int (*init_ssl)(void *ssl_context, void *user_data);
-int (*websocket_connect)(const struct mg_connection *);
-void (*websocket_ready)(struct mg_connection *);
-int (*websocket_data)(struct mg_connection *,
-                      int bits,
-                      char *data,
-                      size_t data_len);
-void (*connection_close)(struct mg_connection *);
-const char *(*open_file)(const struct mg_connection *,
-                         const char *path,
-                         size_t *data_len);
-void (*init_lua)(struct mg_connection *, void *lua_context);
-void (*upload)(struct mg_connection *, const char *file_name);
-int (*http_error)(struct mg_connection *, int status);
-
-static struct mg_callbacks CALLBACKS;
-
-static void init_CALLBACKS()
-{
-	memset(&CALLBACKS, 0, sizeof(CALLBACKS));
-	CALLBACKS.begin_request = begin_request_handler_cb;
-	CALLBACKS.log_message = log_message_cb;
-	CALLBACKS.open_file = open_file_cb;
-#if defined(MG_LEGACY_INTERFACE)
-	CALLBACKS.upload = upload_cb;
-#endif
-};
-
-static const char *OPTIONS[] = {
-    "document_root",
-    ".",
-    "listening_ports",
-    LISTENING_ADDR,
-    "enable_keep_alive",
-    "yes",
-#ifndef NO_SSL
-    "ssl_certificate",
-    "../resources/ssl_cert.pem",
-#endif
-    NULL,
-};
-
-static char *read_conn(struct mg_connection *conn, int *size)
-{
-	char buf[100], *data = NULL;
-	int len;
-	*size = 0;
-	while ((len = mg_read(conn, buf, sizeof(buf))) > 0) {
-		*size += len;
-		data = mg_realloc(data, *size);
-		ASSERT(data != NULL);
-		memcpy(data + *size - len, buf, len);
-	}
-	return data;
-}
-
-#ifdef MEMORY_DEBUGGING
-extern unsigned long mg_memory_debug_blockCount;
-extern unsigned long mg_memory_debug_totalMemUsed;
-#endif
-
-static void ut_mg_stop(struct mg_context *ctx)
-{
-	/* mg_stop for unit_test */
-	mg_stop(ctx);
-#ifdef MEMORY_DEBUGGING
-	ASSERT(mg_memory_debug_blockCount == 0);
-	ASSERT(mg_memory_debug_totalMemUsed == 0);
-	mg_memory_debug_blockCount = 0;
-	mg_memory_debug_totalMemUsed = 0;
-#endif
-
-	mg_sleep(31000); /* This is required to ensure the operating system already
-	                    allows to use the port again */
-}
-
-static void test_mg_download(int use_ssl)
-{
-
-	const char *test_data = "123456789A123456789B";
-
-	char *p1, *p2, ebuf[100];
-	const char *h;
-	int i, len1, len2, port;
-	struct mg_connection *conn;
-	struct mg_context *ctx;
-	const struct mg_request_info *ri;
-
-	if (use_ssl) {
-		port = atoi(HTTPS_PORT);
-	} else {
-		port = atoi(HTTP_PORT);
-	}
-
-	ctx = mg_start(&CALLBACKS, NULL, OPTIONS);
-
-	ASSERT(ctx != NULL);
-
-	ASSERT(mg_download(NULL, port, use_ssl, ebuf, sizeof(ebuf), "%s", "") ==
-	       NULL);
-	ASSERT(mg_download("localhost", 0, use_ssl, ebuf, sizeof(ebuf), "%s", "") ==
-	       NULL);
-	ASSERT(
-	    mg_download("localhost", port, use_ssl, ebuf, sizeof(ebuf), "%s", "") ==
-	    NULL);
-
-	/* Fetch nonexistent file, should see 404 */
-	ASSERT((conn = mg_download("localhost",
-	                           port,
-	                           use_ssl,
-	                           ebuf,
-	                           sizeof(ebuf),
-	                           "%s",
-	                           "GET /gimbec HTTP/1.0\r\n\r\n")) != NULL);
-	ASSERT(strcmp(conn->request_info.uri, "404") == 0);
-	mg_close_connection(conn);
-
-	if (use_ssl) {
-		ASSERT((conn = mg_download("google.com",
-		                           443,
-		                           1,
-		                           ebuf,
-		                           sizeof(ebuf),
-		                           "%s",
-		                           "GET / HTTP/1.0\r\n\r\n")) != NULL);
-		mg_close_connection(conn);
-	} else {
-		ASSERT((conn = mg_download("google.com",
-		                           80,
-		                           0,
-		                           ebuf,
-		                           sizeof(ebuf),
-		                           "%s",
-		                           "GET / HTTP/1.0\r\n\r\n")) != NULL);
-		mg_close_connection(conn);
-	}
-
-	/* Fetch unit_test.c, should succeed */
-	ASSERT((conn = mg_download("localhost",
-	                           port,
-	                           use_ssl,
-	                           ebuf,
-	                           sizeof(ebuf),
-	                           "%s",
-	                           "GET /unit_test.c HTTP/1.0\r\n\r\n")) != NULL);
-	ASSERT(&conn->request_info == mg_get_request_info(conn));
-	ASSERT(!strcmp(conn->request_info.uri, "200"));
-	ASSERT((p1 = read_conn(conn, &len1)) != NULL);
-	ASSERT((p2 = read_file("unit_test.c", &len2)) != NULL);
-	ASSERT(len1 == len2);
-	ASSERT(memcmp(p1, p2, len1) == 0);
-	mg_free(p1);
-	mg_free(p2);
-	mg_close_connection(conn);
-
-	/* Fetch in-memory file, should succeed. */
-	ASSERT((conn = mg_download("localhost",
-	                           port,
-	                           use_ssl,
-	                           ebuf,
-	                           sizeof(ebuf),
-	                           "%s",
-	                           "GET /blah HTTP/1.1\r\n\r\n")) != NULL);
-	ASSERT((p1 = read_conn(conn, &len1)) != NULL);
-	ASSERT(len1 == (int)strlen(inmemory_file_data));
-	ASSERT(memcmp(p1, inmemory_file_data, len1) == 0);
-	mg_free(p1);
-	mg_close_connection(conn);
-
-	/* Fetch in-memory data with no Content-Length, should succeed. */
-	ASSERT((conn = mg_download("localhost",
-	                           port,
-	                           use_ssl,
-	                           ebuf,
-	                           sizeof(ebuf),
-	                           "%s",
-	                           "GET /data/all HTTP/1.1\r\n\r\n")) != NULL);
-	ASSERT(conn->request_info.content_length == fetch_data_size);
-	ASSERT((p1 = read_conn(conn, &len1)) != NULL);
-	ASSERT(len1 == (int)fetch_data_size);
-	ASSERT(memcmp(p1, fetch_data, len1) == 0);
-	mg_free(p1);
-	mg_close_connection(conn);
-
-	/* Fetch in-memory data with no Content-Length, should succeed. */
-	for (i = 0; i <= 1024 * /* 1024 * */ 8; i += (i < 2 ? 1 : i)) {
-		ASSERT((conn = mg_download("localhost",
-		                           port,
-		                           use_ssl,
-		                           ebuf,
-		                           sizeof(ebuf),
-		                           "GET /data/%i HTTP/1.1\r\n\r\n",
-		                           i)) != NULL);
-		ASSERT(conn->request_info.content_length == i);
-		len1 = -1;
-		p1 = read_conn(conn, &len1);
-		if (i == 0) {
-			ASSERT(len1 == 0);
-			ASSERT(p1 == 0);
-		} else if (i <= fetch_data_size) {
-			ASSERT(p1 != NULL);
-			ASSERT(len1 == i);
-			ASSERT(memcmp(p1, fetch_data, len1) == 0);
-		} else {
-			ASSERT(p1 != NULL);
-			ASSERT(len1 == i);
-			ASSERT(memcmp(p1, fetch_data, fetch_data_size) == 0);
-		}
-
-		mg_free(p1);
-		mg_close_connection(conn);
-	}
-
-	/* Fetch data with Content-Length, should succeed and return the defined
-	 * length. */
-	ASSERT((conn = mg_download(
-	            "localhost",
-	            port,
-	            use_ssl,
-	            ebuf,
-	            sizeof(ebuf),
-	            "POST /content_length HTTP/1.1\r\nContent-Length: %u\r\n\r\n%s",
-	            (unsigned)strlen(test_data),
-	            test_data)) != NULL);
-	h = mg_get_header(conn, "Content-Length");
-	ASSERT((h != NULL) && (atoi(h) == (int)strlen(test_data)));
-	ASSERT((p1 = read_conn(conn, &len1)) != NULL);
-	ASSERT(len1 == (int)strlen(test_data));
-	ASSERT(conn->request_info.content_length == (int)strlen(test_data));
-	ASSERT(memcmp(p1, test_data, len1) == 0);
-	ASSERT(strcmp(conn->request_info.request_method, "HTTP/1.1") == 0);
-	ASSERT(strcmp(conn->request_info.uri, "200") == 0);
-	ASSERT(strcmp(conn->request_info.http_version, "OK") == 0);
-	mg_free(p1);
-	mg_close_connection(conn);
-
-	/* A POST request without Content-Length set is only valid, if the request
-	 * used Transfer-Encoding: chunked. Otherwise, it is an HTTP protocol
-	 * violation. Here we send a chunked request, the reply is not chunked. */
-	ASSERT((conn = mg_download("localhost",
-	                           port,
-	                           use_ssl,
-	                           ebuf,
-	                           sizeof(ebuf),
-	                           "POST /content_length "
-	                           "HTTP/1.1\r\n"
-	                           "Transfer-Encoding: chunked\r\n"
-	                           "\r\n%x\r\n%s\r\n0\r\n\r\n",
-	                           (uint32_t)strlen(test_data),
-	                           test_data)) != NULL);
-	h = mg_get_header(conn, "Content-Length");
-	ASSERT(h == NULL);
-	ASSERT(conn->request_info.content_length == -1);
-	ASSERT((p1 = read_conn(conn, &len1)) != NULL);
-	ASSERT(len1 == (int)strlen(test_data));
-	ASSERT(memcmp(p1, test_data, len1) == 0);
-	mg_free(p1);
-	mg_close_connection(conn);
-
-	/* Another chunked POST request with different chunk sizes. */
-	ASSERT((conn = mg_download("localhost",
-	                           port,
-	                           use_ssl,
-	                           ebuf,
-	                           sizeof(ebuf),
-	                           "POST /content_length "
-	                           "HTTP/1.1\r\n"
-	                           "Transfer-Encoding: chunked\r\n\r\n"
-	                           "2\r\n%c%c\r\n"
-	                           "1\r\n%c\r\n"
-	                           "2\r\n%c%c\r\n"
-	                           "2\r\n%c%c\r\n"
-	                           "%x\r\n%s\r\n"
-	                           "0\r\n\r\n",
-	                           test_data[0],
-	                           test_data[1],
-	                           test_data[2],
-	                           test_data[3],
-	                           test_data[4],
-	                           test_data[5],
-	                           test_data[6],
-	                           (uint32_t)strlen(test_data + 7),
-	                           test_data + 7)) != NULL);
-	h = mg_get_header(conn, "Content-Length");
-	ASSERT(h == NULL);
-	ASSERT(conn->request_info.content_length == -1);
-	ASSERT((p1 = read_conn(conn, &len1)) != NULL);
-	ASSERT(len1 == (int)strlen(test_data));
-	ASSERT(memcmp(p1, test_data, len1) == 0);
-	mg_free(p1);
-	mg_close_connection(conn);
-
-	/* Test non existent */
-	ASSERT((conn = mg_download("localhost",
-	                           port,
-	                           use_ssl,
-	                           ebuf,
-	                           sizeof(ebuf),
-	                           "%s",
-	                           "GET /non_exist HTTP/1.1\r\n\r\n")) != NULL);
-	ASSERT(strcmp(conn->request_info.request_method, "HTTP/1.1") == 0);
-	ASSERT(strcmp(conn->request_info.uri, "404") == 0);
-	ASSERT(strcmp(conn->request_info.http_version, "Not Found") == 0);
-	mg_close_connection(conn);
-
-	if (use_ssl) {
-#ifndef NO_SSL
-		/* Test SSL redirect */
-		ASSERT((conn = mg_download("localhost",
-		                           atoi(HTTP_REDIRECT_PORT),
-		                           0,
-		                           ebuf,
-		                           sizeof(ebuf),
-		                           "%s",
-		                           "GET /data/4711 HTTP/1.1\r\n\r\n")) != NULL);
-		ASSERT(strcmp(conn->request_info.uri, "302") == 0);
-		h = mg_get_header(conn, "Location");
-		ASSERT(h != NULL);
-		ASSERT(strcmp(h, "https://127.0.0.1:" HTTPS_PORT "/data/4711") == 0);
-		mg_close_connection(conn);
-#endif
-	}
-
-	/* Test new API */
-	ebuf[0] = 1;
-	conn = mg_connect_client("localhost", port, use_ssl, ebuf, sizeof(ebuf));
-	ASSERT(conn != NULL);
-	ASSERT(ebuf[0] == 0);
-	ri = mg_get_request_info(conn);
-	ASSERT(ri->content_length == 0);
-	i = mg_get_response(conn, ebuf, sizeof(ebuf), 1000);
-	ASSERT(ebuf[0] != 0);
-	ri = mg_get_request_info(conn);
-	ASSERT(ri->content_length == -1);
-	mg_printf(conn, "GET /index.html HTTP/1.1\r\n");
-	mg_printf(conn, "Host: www.example.com\r\n");
-	mg_printf(conn, "\r\n");
-	i = mg_get_response(conn, ebuf, sizeof(ebuf), 1000);
-	ASSERT(ebuf[0] == 0);
-	ri = mg_get_request_info(conn);
-	ASSERT(ri->content_length > 0);
-	mg_read(conn, ebuf, sizeof(ebuf));
-	ASSERT(!strncmp(ebuf, "Error 404", 9));
-
-	mg_close_connection(conn);
-
-	/* Stop the test server */
-	ut_mg_stop(ctx);
-}
-
-static int websocket_data_handler(const struct mg_connection *conn,
-                                  int flags,
-                                  char *data,
-                                  size_t data_len,
-                                  void *cbdata)
-{
-	(void)conn;
-	(void)flags;
-	(void)data;
-	(void)data_len;
-	(void)cbdata;
-	return 1;
-}
-
-static void test_mg_websocket_client_connect(int use_ssl)
-{
-	struct mg_connection *conn;
-	char ebuf[100];
-	int port;
-	struct mg_context *ctx;
-
-	if (use_ssl)
-		port = atoi(HTTPS_PORT);
-	else
-		port = atoi(HTTP_PORT);
-	ASSERT((ctx = mg_start(&CALLBACKS, NULL, OPTIONS)) != NULL);
-
-	/* Try to connect to our own server */
-	/* Invalid port test */
-	conn = mg_connect_websocket_client("localhost",
-	                                   0,
-	                                   use_ssl,
-	                                   ebuf,
-	                                   sizeof(ebuf),
-	                                   "/",
-	                                   "http://localhost",
-	                                   (mg_websocket_data_handler)websocket_data_handler,
-	                                   NULL,
-	                                   NULL);
-	ASSERT(conn == NULL);
-
-	/* Should succeed, the default civetweb server should complete the handshake
-	 */
-	conn = mg_connect_websocket_client("localhost",
-	                                   port,
-	                                   use_ssl,
-	                                   ebuf,
-	                                   sizeof(ebuf),
-	                                   "/",
-	                                   "http://localhost",
-	                                   (mg_websocket_data_handler)websocket_data_handler,
-	                                   NULL,
-	                                   NULL);
-	ASSERT(conn != NULL);
-
-	/* Try an external server test */
-	port = 80;
-	if (use_ssl) {
-		port = 443;
-	}
-
-	/* Not a websocket server path */
-	conn = mg_connect_websocket_client("websocket.org",
-	                                   port,
-	                                   use_ssl,
-	                                   ebuf,
-	                                   sizeof(ebuf),
-	                                   "/",
-	                                   "http://websocket.org",
-	                                   (mg_websocket_data_handler)websocket_data_handler,
-	                                   NULL,
-	                                   NULL);
-	ASSERT(conn == NULL);
-
-	/* Invalid port test */
-	conn = mg_connect_websocket_client("echo.websocket.org",
-	                                   0,
-	                                   use_ssl,
-	                                   ebuf,
-	                                   sizeof(ebuf),
-	                                   "/",
-	                                   "http://websocket.org",
-	                                   (mg_websocket_data_handler)websocket_data_handler,
-	                                   NULL,
-	                                   NULL);
-	ASSERT(conn == NULL);
-
-	/* Should succeed, echo.websocket.org echos the data back */
-	conn = mg_connect_websocket_client("echo.websocket.org",
-	                                   port,
-	                                   use_ssl,
-	                                   ebuf,
-	                                   sizeof(ebuf),
-	                                   "/",
-	                                   "http://websocket.org",
-	                                   (mg_websocket_data_handler)websocket_data_handler,
-	                                   NULL,
-	                                   NULL);
-	ASSERT(conn != NULL);
-
-	ut_mg_stop(ctx);
-}
-
-static int alloc_printf(char **out_buf, char *buf, size_t size, char *fmt, ...)
-{
-	va_list ap;
-	int ret = 0;
-	va_start(ap, fmt);
-	ret = alloc_vprintf(out_buf, buf, size, fmt, ap);
-	va_end(ap);
-	return ret;
-}
-
-#if defined(MG_LEGACY_INTERFACE)
-static void test_mg_upload(void)
-{
-	static const char *boundary = "OOO___MY_BOUNDARY___OOO";
-	struct mg_context *ctx;
-#if 0
-    struct mg_connection *conn;
-    char ebuf[100], buf[20], *file2_data;
-    int file2_len;
-#endif
-	char *file_data, *post_data;
-	int file_len, post_data_len;
-
-	ASSERT((ctx = mg_start(&CALLBACKS, NULL, OPTIONS)) != NULL);
-
-	/* Upload one file */
-	ASSERT((file_data = read_file("unit_test.c", &file_len)) != NULL);
-	post_data = NULL;
-	post_data_len = alloc_printf(&post_data,
-                                 NULL,
-	                             0,
-	                             "--%s\r\n"
-	                             "Content-Disposition: form-data; "
-	                             "name=\"file\"; "
-	                             "filename=\"%s\"\r\n\r\n"
-	                             "%.*s\r\n"
-	                             "--%s--\r\n",
-	                             boundary,
-	                             upload_filename,
-	                             file_len,
-	                             file_data,
-	                             boundary);
-	ASSERT(post_data_len > 0);
-
-#if 0 /* TODO (bel): ... */
-    ASSERT((conn = mg_download("localhost", atoi(HTTPS_PORT), 1,
-        ebuf, sizeof(ebuf),
-        "POST /upload?1 HTTP/1.1\r\n"
-        "Content-Length: %d\r\n"
-        "Content-Type: multipart/form-data; "
-        "boundary=%s\r\n\r\n"
-        "%.*s", post_data_len, boundary,
-        post_data_len, post_data)) != NULL);
-    mg_free(file_data), mg_free(post_data);
-    ASSERT(mg_read(conn, buf, sizeof(buf)) == (int) strlen(upload_ok_message));
-    ASSERT(memcmp(buf, upload_ok_message, strlen(upload_ok_message)) == 0);
-    mg_close_connection(conn);
-
-    /* Upload two files */
-    ASSERT((file_data = read_file("include/civetweb.h", &file_len)) != NULL);
-    ASSERT((file2_data = read_file("README.md", &file2_len)) != NULL);
-    post_data = NULL;
-    post_data_len = alloc_printf(&post_data, 0,
-        /* First file */
-        "--%s\r\n"
-        "Content-Disposition: form-data; "
-        "name=\"file\"; "
-        "filename=\"%s\"\r\n\r\n"
-        "%.*s\r\n"
-
-        /* Second file */
-        "--%s\r\n"
-        "Content-Disposition: form-data; "
-        "name=\"file\"; "
-        "filename=\"%s\"\r\n\r\n"
-        "%.*s\r\n"
-
-        /* Final boundary */
-        "--%s--\r\n",
-        boundary, upload_filename,
-        file_len, file_data,
-        boundary, upload_filename2,
-        file2_len, file2_data,
-        boundary);
-    ASSERT(post_data_len > 0);
-    ASSERT((conn = mg_download("localhost", atoi(HTTPS_PORT), 1,
-        ebuf, sizeof(ebuf),
-        "POST /upload?2 HTTP/1.1\r\n"
-        "Content-Length: %d\r\n"
-        "Content-Type: multipart/form-data; "
-        "boundary=%s\r\n\r\n"
-        "%.*s", post_data_len, boundary,
-        post_data_len, post_data)) != NULL);
-    mg_free(file_data), mg_free(file2_data), mg_free(post_data);
-    ASSERT(mg_read(conn, buf, sizeof(buf)) == (int) strlen(upload_ok_message));
-    ASSERT(memcmp(buf, upload_ok_message, strlen(upload_ok_message)) == 0);
-    mg_close_connection(conn);
-#endif
-
-	ut_mg_stop(ctx);
-}
-#endif
-
-static void test_base64_encode(void)
-{
-	const char *in[] = {"a", "ab", "abc", "abcd", NULL};
-	const char *out[] = {"YQ==", "YWI=", "YWJj", "YWJjZA=="};
-	char buf[100];
-	int i;
-
-	for (i = 0; in [i] != NULL; i++) {
-		base64_encode((unsigned char *)in[i], strlen(in[i]), buf);
-		ASSERT(!strcmp(buf, out[i]));
-	}
-}
-
-static void test_mg_get_var(void)
-{
-	static const char *post[] = {"a=1&&b=2&d&=&c=3%20&e=",
-	                             "q=&st=2012%2F11%2F13+17%3A05&et=&team_id=",
-	                             NULL};
-	char buf[20];
-
-	ASSERT(mg_get_var(post[0], strlen(post[0]), "a", buf, sizeof(buf)) == 1);
-	ASSERT(buf[0] == '1' && buf[1] == '\0');
-	ASSERT(mg_get_var(post[0], strlen(post[0]), "b", buf, sizeof(buf)) == 1);
-	ASSERT(buf[0] == '2' && buf[1] == '\0');
-	ASSERT(mg_get_var(post[0], strlen(post[0]), "c", buf, sizeof(buf)) == 2);
-	ASSERT(buf[0] == '3' && buf[1] == ' ' && buf[2] == '\0');
-	ASSERT(mg_get_var(post[0], strlen(post[0]), "e", buf, sizeof(buf)) == 0);
-	ASSERT(buf[0] == '\0');
-
-	ASSERT(mg_get_var(post[0], strlen(post[0]), "d", buf, sizeof(buf)) == -1);
-	ASSERT(mg_get_var(post[0], strlen(post[0]), "c", buf, 2) == -2);
-
-	ASSERT(mg_get_var(post[0], strlen(post[0]), "x", NULL, 10) == -2);
-	ASSERT(mg_get_var(post[0], strlen(post[0]), "x", buf, 0) == -2);
-	ASSERT(mg_get_var(post[1], strlen(post[1]), "st", buf, 16) == -2);
-	ASSERT(mg_get_var(post[1], strlen(post[1]), "st", buf, 17) == 16);
-}
-
-static void test_set_throttle(void)
-{
-	ASSERT(set_throttle(NULL, 0x0a000001, "/") == 0);
-	ASSERT(set_throttle("10.0.0.0/8=20", 0x0a000001, "/") == 20);
-	ASSERT(set_throttle("10.0.0.0/8=0.5k", 0x0a000001, "/") == 512);
-	ASSERT(set_throttle("10.0.0.0/8=17m", 0x0a000001, "/") == 1048576 * 17);
-	ASSERT(set_throttle("10.0.0.0/8=1x", 0x0a000001, "/") == 0);
-	ASSERT(set_throttle("10.0.0.0/8=5,0.0.0.0/0=10", 0x0a000001, "/") == 10);
-	ASSERT(set_throttle("10.0.0.0/8=5,/foo/**=7", 0x0a000001, "/index") == 5);
-	ASSERT(set_throttle("10.0.0.0/8=5,/foo/**=7", 0x0a000001, "/foo/x") == 7);
-	ASSERT(set_throttle("10.0.0.0/8=5,/foo/**=7", 0x0b000001, "/foxo/x") == 0);
-	ASSERT(set_throttle("10.0.0.0/8=5,*=1", 0x0b000001, "/foxo/x") == 1);
-}
-
-static void test_next_option(void)
-{
-	const char *p, *list = "x/8,/y**=1;2k,z";
-	struct vec a, b;
-	int i;
-
-	ASSERT(next_option(NULL, &a, &b) == NULL);
-	for (i = 0, p = list; (p = next_option(p, &a, &b)) != NULL; i++) {
-		ASSERT(i != 0 || (a.ptr == list && a.len == 3 && b.len == 0));
-		ASSERT(i != 1 || (a.ptr == list + 4 && a.len == 4 &&
-		                  b.ptr == list + 9 && b.len == 4));
-		ASSERT(i != 2 || (a.ptr == list + 14 && a.len == 1 && b.len == 0));
-	}
-}
-
-#if defined(USE_LUA)
-static void check_lua_expr(lua_State *L, const char *expr, const char *value)
-{
-	const char *v, *var_name = "myVar";
-	char buf[100];
-
-	mg_snprintf(buf, sizeof(buf), "%s = %s", var_name, expr);
-	(void)luaL_dostring(L, buf);
-	lua_getglobal(L, var_name);
-	v = lua_tostring(L, -1);
-	ASSERT((value == NULL && v == NULL) ||
-	       (value != NULL && v != NULL && !strcmp(value, v)));
-}
-
-static void test_lua(void)
-{
-	static struct mg_connection conn;
-	static struct mg_context ctx;
-
-	char http_request[] = "POST /foo/bar HTTP/1.1\r\n"
-	                      "Content-Length: 12\r\n"
-	                      "Connection: close\r\n\r\nhello world!";
-	lua_State *L = luaL_newstate();
-
-	conn.ctx = &ctx;
-	conn.buf = http_request;
-	conn.buf_size = conn.data_len = strlen(http_request);
-	conn.request_len =
-	    parse_http_message(conn.buf, conn.data_len, &conn.request_info);
-	conn.content_len = conn.data_len - conn.request_len;
-
-	prepare_lua_environment(&ctx, &conn, NULL, L, "unit_test", LUA_ENV_TYPE_PLAIN_LUA_PAGE);
-	ASSERT(lua_gettop(L) == 4);
-
-	check_lua_expr(L, "'hi'", "hi");
-	check_lua_expr(L, "mg.request_info.request_method", "POST");
-	check_lua_expr(L, "mg.request_info.uri", "/foo/bar");
-	check_lua_expr(L, "mg.request_info.num_headers", "2");
-	check_lua_expr(L, "mg.request_info.remote_ip", "0");
-	check_lua_expr(L, "mg.request_info.http_headers['Content-Length']", "12");
-	check_lua_expr(L, "mg.request_info.http_headers['Connection']", "close");
-	(void)luaL_dostring(L, "post = mg.read()");
-	check_lua_expr(L, "# post", "12");
-	check_lua_expr(L, "post", "hello world!");
-	lua_close(L);
-}
-#endif
-
-static void test_mg_stat(void)
-{
-	static struct mg_context ctx;
-	struct file file = STRUCT_FILE_INITIALIZER;
-	ASSERT(!mg_stat(fc(&ctx), " does not exist ", &file));
-}
-
-static void test_skip_quoted(void)
-{
-	char x[] = "a=1, b=2, c='hi \' there', d='here\\, there'", *s = x, *p;
-
-	p = skip_quoted(&s, ", ", ", ", 0);
-	ASSERT(p != NULL && !strcmp(p, "a=1"));
-
-	p = skip_quoted(&s, ", ", ", ", 0);
-	ASSERT(p != NULL && !strcmp(p, "b=2"));
-
-	p = skip_quoted(&s, ",", " ", 0);
-	ASSERT(p != NULL && !strcmp(p, "c='hi \' there'"));
-
-	p = skip_quoted(&s, ",", " ", '\\');
-	ASSERT(p != NULL && !strcmp(p, "d='here, there'"));
-	ASSERT(*s == 0);
-}
-
-static void test_alloc_vprintf(void)
-{
-	char buf[MG_BUF_LEN], *p = buf;
-
-	ASSERT(alloc_printf(&p, buf, sizeof(buf), "%s", "hi") == 2);
-	ASSERT(p == buf);
-	ASSERT(alloc_printf(&p, buf, sizeof(buf), "%s", "") == 0);
-	ASSERT(alloc_printf(&p, buf, sizeof(buf), "") == 0);
-
-	/* Pass small buffer, make sure alloc_printf allocates */
-	ASSERT(alloc_printf(&p, buf, 1, "%s", "hello") == 5);
-	ASSERT(p != buf);
-	mg_free(p);
-}
-
-static void test_request_replies(void)
-{
-	char ebuf[100];
-	int i;
-	struct mg_connection *conn;
-	struct mg_context *ctx;
-	static struct {
-		const char *request, *reply_regex;
-	} tests[] = {
-	    {"GET hello.txt HTTP/1.0\r\nRange: bytes=3-5\r\n\r\n",
-	     "^HTTP/1.1 206 Partial Content"},
-	    {NULL, NULL},
-	};
-
-	ASSERT((ctx = mg_start(&CALLBACKS, NULL, OPTIONS)) != NULL);
-	for (i = 0; tests[i].request != NULL; i++) {
-		ASSERT((conn = mg_download("localhost",
-		                           atoi(HTTP_PORT),
-		                           0,
-		                           ebuf,
-		                           sizeof(ebuf),
-		                           "%s",
-		                           tests[i].request)) != NULL);
-		mg_close_connection(conn);
-	}
-	ut_mg_stop(ctx);
-
-#ifndef NO_SSL
-	ASSERT((ctx = mg_start(&CALLBACKS, NULL, OPTIONS)) != NULL);
-	for (i = 0; tests[i].request != NULL; i++) {
-		ASSERT((conn = mg_download("localhost",
-		                           atoi(HTTPS_PORT),
-		                           1,
-		                           ebuf,
-		                           sizeof(ebuf),
-		                           "%s",
-		                           tests[i].request)) != NULL);
-		mg_close_connection(conn);
-	}
-	ut_mg_stop(ctx);
-#endif
-}
-
-static int request_test_handler(struct mg_connection *conn, void *cbdata)
-{
-	int i;
-	char chunk_data[32];
-
-	ASSERT(cbdata == (void *)7);
-	strcpy(chunk_data, "123456789A123456789B123456789C");
-
-	mg_printf(conn,
-	          "HTTP/1.1 200 OK\r\n"
-	          "Transfer-Encoding: chunked\r\n"
-	          "Content-Type: text/plain\r\n\r\n");
-
-	for (i = 0; i < 20; i++) {
-		mg_printf(conn, "%x\r\n", i);
-		mg_write(conn, chunk_data, i);
-		mg_printf(conn, "\r\n");
-	}
-
-	mg_printf(conn, "0\r\n\r\n");
-
-	return 1;
-}
-
-
-static void test_request_handlers(void)
-{
-	char ebuf[100];
-	struct mg_context *ctx;
-	struct mg_connection *conn;
-	char uri[64];
-	int i;
-	const char *request = "GET /U7 HTTP/1.0\r\n\r\n";
-
-	ctx = mg_start(NULL, NULL, OPTIONS);
-	ASSERT(ctx != NULL);
-
-	for (i = 0; i < 1000; i++) {
-		sprintf(uri, "/U%u", i);
-		mg_set_request_handler(ctx, uri, request_test_handler, NULL);
-	}
-	for (i = 500; i < 800; i++) {
-		sprintf(uri, "/U%u", i);
-		mg_set_request_handler(ctx, uri, NULL, (void *)1);
-	}
-	for (i = 600; i >= 0; i--) {
-		sprintf(uri, "/U%u", i);
-		mg_set_request_handler(ctx, uri, NULL, (void *)2);
-	}
-	for (i = 750; i <= 1000; i++) {
-		sprintf(uri, "/U%u", i);
-		mg_set_request_handler(ctx, uri, NULL, (void *)3);
-	}
-	for (i = 5; i < 9; i++) {
-		sprintf(uri, "/U%u", i);
-		mg_set_request_handler(ctx, uri, request_test_handler, (void *)(intptr_t)i);
-	}
-
-	conn = mg_download(
-	    "localhost", atoi(HTTP_PORT), 0, ebuf, sizeof(ebuf), "%s", request);
-	ASSERT(conn != NULL);
-	mg_sleep(1000);
-	mg_close_connection(conn);
-
-	ut_mg_stop(ctx);
-}
-
-static int api_callback(struct mg_connection *conn)
-{
-	const struct mg_request_info *ri = mg_get_request_info(conn);
-	char post_data[100] = "";
-
-	ASSERT(ri->user_data == (void *)123);
-	ASSERT(ri->num_headers == 2);
-	ASSERT(strcmp(mg_get_header(conn, "host"), "blah.com") == 0);
-	ASSERT(mg_read(conn, post_data, sizeof(post_data)) == 3);
-	ASSERT(memcmp(post_data, "b=1", 3) == 0);
-	ASSERT(ri->query_string != NULL);
-	ASSERT(ri->remote_addr[0] != 0);
-	ASSERT(ri->remote_port > 0);
-	ASSERT(strcmp(ri->http_version, "1.0") == 0);
-
-	mg_printf(conn, "HTTP/1.0 200 OK\r\n\r\n");
-	return 1;
-}
-
-static void test_api_calls(void)
-{
-	char ebuf[100];
-	struct mg_callbacks callbacks;
-	struct mg_connection *conn;
-	struct mg_context *ctx;
-	static const char *request =
-	    "POST /?a=%20&b=&c=xx HTTP/1.0\r\n"
-	    "Host:  blah.com\n"     /* More spaces before */
-	    "content-length: 3\r\n" /* Lower case header name */
-	    "\r\nb=123456"; /* Content size > content-length, test for mg_read() */
-
-	memset(&callbacks, 0, sizeof(callbacks));
-	callbacks.begin_request = api_callback;
-	ASSERT((ctx = mg_start(&callbacks, (void *)123, OPTIONS)) != NULL);
-	ASSERT((conn = mg_download("localhost",
-	                           atoi(HTTP_PORT),
-	                           0,
-	                           ebuf,
-	                           sizeof(ebuf),
-	                           "%s",
-	                           request)) != NULL);
-	mg_close_connection(conn);
-	ut_mg_stop(ctx);
-}
-
-static void test_url_decode(void)
-{
-	char buf[100];
-
-	ASSERT(mg_url_decode("foo", 3, buf, 3, 0) == -1); /* No space for \0 */
-	ASSERT(mg_url_decode("foo", 3, buf, 4, 0) == 3);
-	ASSERT(strcmp(buf, "foo") == 0);
-
-	ASSERT(mg_url_decode("a+", 2, buf, sizeof(buf), 0) == 2);
-	ASSERT(strcmp(buf, "a+") == 0);
-
-	ASSERT(mg_url_decode("a+", 2, buf, sizeof(buf), 1) == 2);
-	ASSERT(strcmp(buf, "a ") == 0);
-
-	ASSERT(mg_url_decode("%61", 1, buf, sizeof(buf), 1) == 1);
-	ASSERT(strcmp(buf, "%") == 0);
-
-	ASSERT(mg_url_decode("%61", 2, buf, sizeof(buf), 1) == 2);
-	ASSERT(strcmp(buf, "%6") == 0);
-
-	ASSERT(mg_url_decode("%61", 3, buf, sizeof(buf), 1) == 1);
-	ASSERT(strcmp(buf, "a") == 0);
-}
-
-static void test_mg_strcasestr(void)
-{
-	static const char *big1 = "abcdef";
-	ASSERT(mg_strcasestr("Y", "X") == NULL);
-	ASSERT(mg_strcasestr("Y", "y") != NULL);
-	ASSERT(mg_strcasestr(big1, "X") == NULL);
-	ASSERT(mg_strcasestr(big1, "CD") == big1 + 2);
-	ASSERT(mg_strcasestr("aa", "AAB") == NULL);
-}
-
-static void test_mg_get_cookie(void)
-{
-	char buf[20];
-
-	ASSERT(mg_get_cookie("", "foo", NULL, sizeof(buf)) == -2);
-	ASSERT(mg_get_cookie("", "foo", buf, 0) == -2);
-	ASSERT(mg_get_cookie("", "foo", buf, sizeof(buf)) == -1);
-	ASSERT(mg_get_cookie("", NULL, buf, sizeof(buf)) == -1);
-	ASSERT(mg_get_cookie("a=1; b=2; c; d", "a", buf, sizeof(buf)) == 1);
-	ASSERT(strcmp(buf, "1") == 0);
-	ASSERT(mg_get_cookie("a=1; b=2; c; d", "b", buf, sizeof(buf)) == 1);
-	ASSERT(strcmp(buf, "2") == 0);
-	ASSERT(mg_get_cookie("a=1; b=123", "b", buf, sizeof(buf)) == 3);
-	ASSERT(strcmp(buf, "123") == 0);
-	ASSERT(mg_get_cookie("a=1; b=2; c; d", "c", buf, sizeof(buf)) == -1);
-}
-
-static void test_strtoll(void)
-{
-	ASSERT(strtoll("0", NULL, 10) == 0);
-	ASSERT(strtoll("123", NULL, 10) == 123);
-	ASSERT(strtoll("-34", NULL, 10) == -34);
-	ASSERT(strtoll("3566626116", NULL, 10) == 3566626116);
-}
-
-static void test_parse_port_string(void)
-{
-	static const char *valid[] = {
-		"0",
-		"1",
-		"1s",
-		"1r",
-		"1.2.3.4:1",
-		"1.2.3.4:1s",
-		"1.2.3.4:1r",
-#if defined(USE_IPV6)
-		"[::1]:123",
-		"[3ffe:2a00:100:7031::1]:900",
-#endif
-		NULL
-	};
-	static const char *invalid[] = {
-	    "99999", "1k", "1.2.3", "1.2.3.4:", "1.2.3.4:2p", NULL};
-	struct socket so;
-	struct vec vec;
-	int ipv;
-	int i;
-
-	for (i = 0; valid[i] != NULL; i++) {
-		vec.ptr = valid[i];
-		vec.len = strlen(vec.ptr);
-		ASSERT(parse_port_string(&vec, &so, &ipv) != 0);
-	}
-
-	for (i = 0; invalid[i] != NULL; i++) {
-		vec.ptr = invalid[i];
-		vec.len = strlen(vec.ptr);
-		ASSERT(parse_port_string(&vec, &so, &ipv) == 0);
-	}
-}
-
-static void test_md5(void)
-{
-
-	md5_state_t md5_state;
-	unsigned char md5_val[16 + 1];
-	char md5_str[32 + 1];
-	const char *test_str = "The quick brown fox jumps over the lazy dog";
-
-	md5_val[16] = 0;
-	md5_init(&md5_state);
-	md5_finish(&md5_state, md5_val);
-	ASSERT(strcmp((const char *)md5_val,
-	              "\xd4\x1d\x8c\xd9\x8f\x00\xb2\x04\xe9"
-	              "\x80\x09\x98\xec\xf8\x42\x7e") == 0);
-	sprintf(md5_str,
-	        "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
-	        md5_val[0],
-	        md5_val[1],
-	        md5_val[2],
-	        md5_val[3],
-	        md5_val[4],
-	        md5_val[5],
-	        md5_val[6],
-	        md5_val[7],
-	        md5_val[8],
-	        md5_val[9],
-	        md5_val[10],
-	        md5_val[11],
-	        md5_val[12],
-	        md5_val[13],
-	        md5_val[14],
-	        md5_val[15]);
-	ASSERT(strcmp(md5_str, "d41d8cd98f00b204e9800998ecf8427e") == 0);
-
-	mg_md5(md5_str, "", NULL);
-	ASSERT(strcmp(md5_str, "d41d8cd98f00b204e9800998ecf8427e") == 0);
-
-	md5_init(&md5_state);
-	md5_append(&md5_state, (const md5_byte_t *)test_str, strlen(test_str));
-	md5_finish(&md5_state, md5_val);
-	sprintf(md5_str,
-	        "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
-	        md5_val[0],
-	        md5_val[1],
-	        md5_val[2],
-	        md5_val[3],
-	        md5_val[4],
-	        md5_val[5],
-	        md5_val[6],
-	        md5_val[7],
-	        md5_val[8],
-	        md5_val[9],
-	        md5_val[10],
-	        md5_val[11],
-	        md5_val[12],
-	        md5_val[13],
-	        md5_val[14],
-	        md5_val[15]);
-	ASSERT(strcmp(md5_str, "9e107d9d372bb6826bd81d3542a419d6") == 0);
-
-	mg_md5(md5_str, test_str, NULL);
-	ASSERT(strcmp(md5_str, "9e107d9d372bb6826bd81d3542a419d6") == 0);
-
-	mg_md5(md5_str,
-	       "The",
-	       " ",
-	       "quick brown fox",
-	       "",
-	       " jumps ",
-	       "over the lazy dog",
-	       "",
-	       "",
-	       NULL);
-	ASSERT(strcmp(md5_str, "9e107d9d372bb6826bd81d3542a419d6") == 0);
-
-	mg_md5(md5_str, "civetweb", NULL);
-	ASSERT(strcmp(md5_str, "95c098bd85b619b24a83d9cea5e8ba54") == 0);
-}
-
-int __cdecl main(void)
-{
-
-	char buffer[512];
-	FILE *f;
-	struct mg_context *ctx;
-	int i;
-
-	/* print headline */
-	printf("Civetweb %s unit test\n", mg_version());
-#if defined(_WIN32)
-	GetCurrentDirectoryA(sizeof(buffer), buffer);
-#else
-	getcwd(buffer, sizeof(buffer));
-#endif
-	printf("Test directory is \"%s\"\n",
-	       buffer); /* should be the "test" directory */
-	f = fopen("hello.txt", "r");
-	if (f) {
-		fclose(f);
-	} else {
-		printf("Error: Test directory does not contain hello.txt\n");
-	}
-	f = fopen("unit_test.c", "r");
-	if (f) {
-		fclose(f);
-	} else {
-		printf("Error: Test directory does not contain unit_test.c\n");
-	}
-
-	/* test local functions */
-	test_parse_port_string();
-	test_mg_strcasestr();
-	test_alloc_vprintf();
-	test_base64_encode();
-	test_match_prefix();
-	test_remove_double_dots();
-	test_should_keep_alive();
-	test_parse_http_message();
-	test_mg_get_var();
-	test_set_throttle();
-	test_next_option();
-	test_mg_stat();
-	test_skip_quoted();
-	test_url_decode();
-	test_mg_get_cookie();
-	test_strtoll();
-	test_md5();
-
-	/* start stop server */
-	ctx = mg_start(NULL, NULL, OPTIONS);
-	REQUIRE(ctx != NULL);
-	mg_sleep(1000);
-	ut_mg_stop(ctx);
-
-	/* create test data */
-	fetch_data = (char *)mg_malloc(fetch_data_size);
-	for (i = 0; i < fetch_data_size; i++) {
-		fetch_data[i] = 'a' + i % 10;
-	}
-
-	/* tests with network access */
-	init_CALLBACKS();
-	test_mg_download(0);
-#ifndef NO_SSL
-	test_mg_download(1);
-#endif
-
-	test_mg_websocket_client_connect(0);
-#ifndef NO_SSL
-	test_mg_websocket_client_connect(1);
-#endif
-
-#if defined(MG_LEGACY_INTERFACE)
-	test_mg_upload();
-#endif
-	test_request_replies();
-	test_api_calls();
-	test_request_handlers();
-
-#if defined(USE_LUA)
-	test_lua();
-#endif
-
-	/* test completed */
-	mg_free(fetch_data);
-
-#ifdef MEMORY_DEBUGGING
-	{
-		extern unsigned long mg_memory_debug_blockCount;
-		extern unsigned long mg_memory_debug_totalMemUsed;
-
-		printf("MEMORY DEBUGGING: %lu %lu\n",
-		       mg_memory_debug_blockCount,
-		       mg_memory_debug_totalMemUsed);
-	}
-#endif
-
-	printf("TOTAL TESTS: %d, FAILED: %d\n", s_total_tests, s_failed_tests);
-	return s_failed_tests == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
-}