瀏覽代碼

Merge remote-tracking branch 'upstream/master'

kakwa 8 年之前
父節點
當前提交
ca728e5140
共有 70 個文件被更改,包括 1401 次插入2084 次删除
  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
 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}")
 if ("${CIVETWEB_VERSION_MATCH}" STREQUAL "")
   message(FATAL_ERROR "Must specify a semantic version: major.minor.patch")
@@ -416,14 +416,16 @@ add_subdirectory(src)
 include(CTest)
 if (BUILD_TESTING)
   # 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")
   set_property(CACHE CIVETWEB_CHECK_VERSION PROPERTY VALUE ${CIVETWEB_CHECK_VERSION})
   message(STATUS "Check Unit Testing Framework Version - ${CIVETWEB_CHECK_VERSION}")
   mark_as_advanced(CIVETWEB_CHECK_VERSION)
 
   # 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")
   set_property(CACHE CIVETWEB_CHECK_MD5_HASH PROPERTY VALUE ${CIVETWEB_CHECK_MD5_HASH})
   mark_as_advanced(CIVETWEB_CHECK_MD5_HASH)

+ 6 - 3
Qt/CivetWeb.pro

@@ -5,13 +5,15 @@ CONFIG -= qt
 
 SOURCES += \
     ../src/md5.inl \
+    ../src/sha1.inl \
+    ../src/handle_form.inl \
     ../src/mod_lua.inl \
     ../src/timer.inl \
     ../src/civetweb.c \
     ../src/main.c
 
-include(deployment.pri)
-qtcAddDeployment()
+#include(deployment.pri)
+#qtcAddDeployment()
 
 HEADERS += \
     ../include/civetweb.h
@@ -19,7 +21,8 @@ HEADERS += \
 INCLUDEPATH +=  \
     ../include/
 
-LIBS += -lws2_32 -lComdlg32
+LIBS += -lws2_32 -lComdlg32 -lUser32 -lShell32 -lAdvapi32
+
 
 DEFINES += USE_IPV6
 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)
 
-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)
 
@@ -49,6 +49,9 @@ Trouble tickets should be filed on GitHub
 Discussion/support group and announcements are at Google Groups
 [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
 --------------------------
@@ -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/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.
+- [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
 - [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.
 
 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).
 
@@ -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)
 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).
 
 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.
-

+ 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
 -------
 
+- 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
 - Update Duktape and Lua (third-party code)
 - Add continuous integration tests
@@ -29,6 +45,7 @@ Changes
 - Read client certificate information
 - Do not tolerate URIs with invalid characters
 - Fix mg_get_cookie to ignore substrings
+- Fix memory leak in form handling
 - Fix bug in timer logic (for Lua Websockets)
 - 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 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
 correct Content-Length HTTP header for each request. If this is forgotten the
 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 
 seconds, so the value should be an integer multiple of 1000.
 
-
 ### lua\_preload\_file
 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
@@ -415,6 +414,18 @@ directly to the client. Lua script parts are delimited from the standard
 content by including them between <? and ?> tags.
 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
 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

+ 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
 * 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
  * License http://opensource.org/licenses/mit-license.php MIT License
  */
@@ -204,12 +204,29 @@ class FooHandler : public CivetHandler
 
 #ifdef _WIN32
         _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
         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
 
         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
  *
  * 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
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -23,7 +23,9 @@
 #ifndef 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
 #if defined(_WIN32)
@@ -49,6 +51,25 @@ extern "C" {
 #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_connection; /* Handle for the individual connection */
 
@@ -1049,9 +1070,16 @@ CIVETWEB_API int mg_get_response(struct mg_connection *conn,
                                  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:
      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)
          2  support HTTPS (NO_SSL 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)
         64  support server side JavaScript (USE_DUKTAPE is 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:
-     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);
 
 
-/* Check which features where set when civetweb has been compiled.
+/* Get information on the system. Useful, if in support requests.
    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:
-     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
 }

+ 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
 #

+ 1 - 1
resources/Makefile.in-lua

@@ -1,6 +1,6 @@
 #
 # 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
 #

+ 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
  *
  * 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
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -52,7 +52,7 @@
 #endif
 #endif
 
-#if defined(USE_LUA) && defined(USE_WEBSOCKET)
+#if defined(USE_LUA)
 #define USE_TIMERS
 #endif
 
@@ -737,8 +737,8 @@ gmtime(const time_t *ptime)
 static size_t
 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;
 }
 
@@ -793,9 +793,10 @@ stat(const char *name, struct stat *st)
 #define access(x, a) 1 /* not required anyway */
 
 /* 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__)
 /* Enable unused function warning again */
@@ -1657,8 +1658,10 @@ struct mg_file_in_memory {
 struct mg_file_access {
 	/* File properties filled by mg_fopen: */
 	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 {
@@ -1761,6 +1764,9 @@ enum {
 #if defined(_WIN32)
 	CASE_SENSITIVE_FILES,
 #endif
+#if defined(USE_LUA)
+	LUA_BACKGROUND_SCRIPT,
+#endif
 
 	NUM_OPTIONS
 };
@@ -1843,6 +1849,9 @@ static struct mg_option config_options[] = {
 #if defined(_WIN32)
     {"case_sensitive", CONFIG_TYPE_BOOLEAN, "no"},
 #endif
+#if defined(USE_LUA)
+    {"lua_background_script", CONFIG_TYPE_FILE, NULL},
+#endif
 
     {NULL, CONFIG_TYPE_UNKNOWN, NULL}};
 
@@ -1913,8 +1922,10 @@ struct mg_context {
 
 	pthread_t masterthreadid; /* The master thread ID */
 	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 */
 	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;
 #endif
 
-#ifdef USE_TIMERS
+#if defined(USE_TIMERS)
 	struct ttimers *timers;
 #endif
+
+#if defined(USE_LUA)
+	void *lua_background_state;
+#endif
 };
 
 
@@ -4663,7 +4678,18 @@ push(struct mg_context *ctx,
 		} else {
 			n = (int)send(sock, buf, (len_t)len, MSG_NOSIGNAL);
 			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 */
 				return -1;
 			}
@@ -4689,8 +4715,11 @@ push(struct mg_context *ctx,
 			/* socket error - check errno */
 			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.
+			 * Currently there is no problem with failing send calls,
+			 * if there is a reproducible situation, it should be
+			 * investigated in detail.
 			 */
 			return -1;
 		}
@@ -4862,11 +4891,11 @@ pull(FILE *fp, struct mg_connection *conn, char *buf, int len, double timeout)
 /* socket error - check errno */
 #ifdef _WIN32
 		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 */
 			return -1;
 		} 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  */
 			return 0;
 		} 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.
 		 */
 		if (err == EAGAIN || err == EWOULDBLOCK || err == EINTR) {
-			/* TODO: check if this is still required */
+			/* TODO (low): check if this is still required */
 			/* EAGAIN/EWOULDBLOCK:
 			 * standard case if called from close_socket_gracefully
 			 * => 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? */
               )
 {
-/* TODO (high): Restructure this function */
+/* TODO (high / maintainability issue): Restructure this function */
 
 #if !defined(NO_FILES)
 	const char *uri = conn->request_info.local_uri;
@@ -6180,10 +6209,10 @@ open_auth_file(struct mg_connection *conn,
 #endif
 			}
 			/* 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)
 		           && filep->stat.is_directory) {
 			mg_snprintf(conn,
@@ -8840,7 +8869,9 @@ mkcol(struct mg_connection *conn, const char *path)
 	}
 
 	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(
 		    conn, 405, "Error: mkcol(%s): %s", path, strerror(ERRNO));
 		return;
@@ -8983,7 +9014,7 @@ put_file(struct mg_connection *conn, const char *path)
 	}
 
 	/* 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)
 	    || file.access.fp == NULL) {
 		(void)mg_fclose(&file.access);
@@ -9367,10 +9398,12 @@ send_options(struct mg_connection *conn)
 	conn->must_close = 1;
 	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,
 	          "HTTP/1.1 200 OK\r\n"
 	          "Date: %s\r\n"
-	          /* TODO: "Cache-Control" (?) */
 	          "Connection: %s\r\n"
 	          "Allow: GET, POST, HEAD, CONNECT, PUT, DELETE, OPTIONS, "
 	          "PROPFIND, MKCOL\r\n"
@@ -9954,8 +9987,9 @@ handle_websocket_request(struct mg_connection *conn,
 				curSubProtocol = protocol;
 				len = sep ? (unsigned long)(sep - protocol)
 				          : (unsigned long)strlen(protocol);
-				while (sep && isspace(*++sep))
-					; // ignore leading whitespaces
+				while (sep && isspace(*++sep)) {
+					; /* ignore leading whitespaces */
+				}
 				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
 				 * offered.
 				 */
-				while (isspace(*++sep))
-					; // ignore leading whitespaces
+				while (isspace(*++sep)) {
+					; /* ignore leading whitespaces */
+				}
 				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. */
 	if (!is_callback_resource && !lua_websock) {
 		/* 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");
 		return;
 	}
@@ -10963,11 +10998,15 @@ handle_request(struct mg_connection *conn)
 					              &is_put_or_delete_request);
 					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;
 				}
 			} else {
@@ -12736,8 +12775,8 @@ mg_close_connection(struct mg_connection *conn)
 
 		/* join worker thread */
 		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) {
 		/* free context */
-		mg_free(client_ctx->workerthreadids);
+		mg_free(client_ctx->worker_threadids);
 		mg_free(client_ctx);
 		(void)pthread_mutex_destroy(&conn->mutex);
 		mg_free(conn);
@@ -13493,7 +13532,7 @@ mg_connect_websocket_client(const char *host,
 	newctx->user_data = user_data;
 	newctx->context_type = 2;       /* ws/wss client context type */
 	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));
 	conn->ctx = newctx;
 	thread_data = (struct websocket_client_thread_data *)
@@ -13508,9 +13547,9 @@ mg_connect_websocket_client(const char *host,
 	 * called on the client connection */
 	if (mg_start_thread_with_id(websocket_client_thread,
 	                            (void *)thread_data,
-	                            newctx->workerthreadids) != 0) {
+	                            newctx->worker_threadids) != 0) {
 		mg_free((void *)thread_data);
-		mg_free((void *)newctx->workerthreadids);
+		mg_free((void *)newctx->worker_threadids);
 		mg_free((void *)newctx);
 		mg_free((void *)conn);
 		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);
 #endif
 
+	/* Initialize thread local storage before calling any callback */
+	pthread_setspecific(sTlsKey, &tls);
+
 	if (ctx->callbacks.init_thread) {
 		/* call init_thread for a worker thread (type 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,
  * error handler would have the corresponding info.
  * Thanks to Johannes Winkelmann for the patch.
  */
 #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
-			{
-				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)
-			/* 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
 
-			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
-				/* 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);
+
+				/* 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);
 #if defined(_WIN32) && !defined(__SYMBIAN32__)
 	CloseHandle(tls.pthread_cond_helper_mutex);
 #endif
 	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");
 	return NULL;
@@ -13969,7 +14026,6 @@ accept_new_connection(const struct socket *listener, struct mg_context *ctx)
 	char src_addr[IP_ADDR_STR_LEN];
 	socklen_t len = sizeof(so.rsa);
 	int on = 1;
-	int timeout;
 
 	if (!listener) {
 		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);
 
 		produce_socket(ctx, &so);
@@ -14142,11 +14189,19 @@ master_thread_run(void *thread_func_param)
 	/* Join all worker threads to avoid leaking threads. */
 	workerthreadcount = ctx->cfg_worker_threads;
 	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 (ctx->ssl_ctx != NULL) {
 		uninitialize_ssl(ctx);
@@ -14245,8 +14300,13 @@ free_context(struct mg_context *ctx)
 #endif /* !NO_SSL */
 
 	/* 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 */
@@ -14508,6 +14568,24 @@ mg_start(const struct mg_callbacks *callbacks,
 
 	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
 	 * be initialized before listening ports. UID must be set last. */
 	if (!set_gpass_option(ctx) ||
@@ -14531,21 +14609,31 @@ mg_start(const struct mg_callbacks *callbacks,
 #endif /* !_WIN32 && !__SYMBIAN32__ */
 
 	ctx->cfg_worker_threads = ((unsigned int)(workerthreadcount));
-	ctx->workerthreadids =
+	ctx->worker_threadids =
 	    (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");
 		free_context(ctx);
 		pthread_setspecific(sTlsKey, 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)
 	ctx->client_wait_events =
 	    mg_calloc(sizeof(ctx->client_wait_events[0]), ctx->cfg_worker_threads);
 	if (ctx->client_wait_events == NULL) {
 		mg_cry(fc(ctx), "Not enough memory for worker event array");
-		mg_free(ctx->workerthreadids);
+		mg_free(ctx->worker_threadids);
 		free_context(ctx);
 		pthread_setspecific(sTlsKey, NULL);
 		return NULL;
@@ -14556,7 +14644,7 @@ mg_start(const struct mg_callbacks *callbacks,
 	if (ctx->client_wait_events == NULL) {
 		mg_cry(fc(ctx), "Not enough memory for worker socket array");
 		mg_free(ctx->client_socks);
-		mg_free(ctx->workerthreadids);
+		mg_free(ctx->worker_threadids);
 		free_context(ctx);
 		pthread_setspecific(sTlsKey, NULL);
 		return NULL;
@@ -14566,7 +14654,15 @@ mg_start(const struct mg_callbacks *callbacks,
 		ctx->client_wait_events[i] = event_create();
 		if (ctx->client_wait_events[i] == 0) {
 			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
@@ -14603,7 +14699,7 @@ mg_start(const struct mg_callbacks *callbacks,
 		if ((wta == NULL)
 		    || (mg_start_thread_with_id(worker_thread,
 		                                wta,
-		                                &ctx->workerthreadids[i]) != 0)) {
+		                                &ctx->worker_threadids[i]) != 0)) {
 
 			/* thread was not created */
 			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(__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
 #pragma warning(push)
-// GetVersion was declared deprecated
+/* GetVersion was declared deprecated */
 #pragma warning(disable : 4996)
 #endif
-	dwVersion = GetVersion();
+		dwVersion = GetVersion();
 #ifdef _MSC_VER
 #pragma warning(pop)
 #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
-	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
 #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
-
-	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
-	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
+#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)
-	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__)
-	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__)
-	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__)
-	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__)
-	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)
-	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__)
-	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)
-	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
-	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
+	}
+
+
 	/* Determine 32/64 bit data mode.
 	 * 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;
 }
 
+
+/* 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 */

+ 2 - 1
src/civetweb_private_lua.h

@@ -5,6 +5,7 @@
 #ifndef 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

+ 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
  * 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
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -103,7 +103,6 @@
 #if !defined(__MINGW32__)
 extern char *_getcwd(char *buf, size_t size);
 #endif
-static int sGuard = 0; /* test if any dialog is already open */
 
 #ifndef PATH_MAX
 #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 const char *g_server_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] =
     "";                          /* Set by process_command_line_arguments() */
 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
 log_message(const struct mg_connection *conn, const char *message)
 {
@@ -756,60 +776,8 @@ set_absolute_path(char *options[],
 
 #ifdef USE_LUA
 
-#include "civetweb_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
 
 
@@ -822,10 +790,6 @@ run_duktape(const char *file_name)
 {
 	duk_context *ctx = NULL;
 
-#ifdef WIN32
-	(void)MakeConsole();
-#endif
-
 	ctx = duk_create_heap_default();
 	if (!ctx) {
 		fprintf(stderr, "Failed to create a Duktape heap.\n");
@@ -867,8 +831,11 @@ start_civetweb(int argc, char *argv[])
 #ifdef WIN32
 		(void)MakeConsole();
 #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);
 	}
@@ -902,6 +869,9 @@ start_civetweb(int argc, char *argv[])
 		if (argc != 3) {
 			show_usage_and_exit(argv[0]);
 		}
+#ifdef WIN32
+		(void)MakeConsole();
+#endif
 		exit(run_lua(argv[2]));
 #else
 		show_server_name();
@@ -917,6 +887,9 @@ start_civetweb(int argc, char *argv[])
 		if (argc != 3) {
 			show_usage_and_exit(argv[0]);
 		}
+#ifdef WIN32
+		(void)MakeConsole();
+#endif
 		exit(run_duktape(argv[2]));
 #else
 		show_server_name();
@@ -1039,7 +1012,6 @@ static SERVICE_STATUS_HANDLE hStatus;
 static const char *service_magic_argument = "--";
 static NOTIFYICONDATA TrayIcon;
 
-
 static void WINAPI
 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
 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();
 	char *file_options[MAX_OPTIONS * 2 + 1] = {0};
 	char *title;
-	(void)lParam;
+	struct dlg_proc_param *pdlg_proc_param;
 
 	switch (msg) {
 
@@ -1261,7 +1244,9 @@ SettingsDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
 	case WM_INITDIALOG:
 		/* Store hWnd in a parameter accessible by the parent, so we can
 		 * 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 */
 		SendMessage(hDlg, WM_SETICON, (WPARAM)ICON_SMALL, (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
-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;
+	HWND hIn;
 
 	switch (msg) {
 	case WM_CLOSE:
@@ -1308,11 +1289,18 @@ InputDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lP)
 	case WM_COMMAND:
 		ctrlId = LOWORD(wParam);
 		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);
 			}
 		} else if (ctrlId == IDCANCEL) {
@@ -1321,17 +1309,30 @@ InputDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lP)
 		break;
 
 	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;
 
 	default:
@@ -1392,7 +1393,7 @@ get_password(const char *user,
 	DLGTEMPLATE *dia = (DLGTEMPLATE *)mem;
 	int ok;
 	short y;
-	struct tstring_input_buf dlgprms;
+	static struct dlg_proc_param s_dlg_proc_param;
 
 	static struct {
 		DLGTEMPLATE template; /* 18 bytes */
@@ -1414,14 +1415,20 @@ get_password(const char *user,
 	                   8,
 	                   L"Tahoma"};
 
-	dlgprms.buffer = passwd;
-	dlgprms.buflen = passwd_len;
-
 	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 {
+		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;
 	}
 
@@ -1429,6 +1436,10 @@ get_password(const char *user,
 	memset(passwd, 0, passwd_len);
 	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 */
 	(void)memset(mem, 0, sizeof(mem));
 	(void)memcpy(mem, &dialog_header, sizeof(dialog_header));
@@ -1450,7 +1461,7 @@ get_password(const char *user,
 	            0x81,
 	            ID_CONTROLS + 1,
 	            WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL
-	                | WS_DISABLED,
+	                | ES_READONLY,
 	            15 + LABEL_WIDTH,
 	            y,
 	            WIDTH - LABEL_WIDTH - 25,
@@ -1473,7 +1484,7 @@ get_password(const char *user,
 	            0x81,
 	            ID_CONTROLS + 2,
 	            WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL
-	                | WS_DISABLED,
+	                | ES_READONLY,
 	            15 + LABEL_WIDTH,
 	            y,
 	            WIDTH - LABEL_WIDTH - 25,
@@ -1528,10 +1539,14 @@ get_password(const char *user,
 
 	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;
 
@@ -1541,12 +1556,14 @@ get_password(const char *user,
 }
 
 
+/* Dialog proc for password dialog */
 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;
 	char domain[256], user[256], password[256];
 	WORD ctrlId;
+	struct dlg_proc_param *pdlg_proc_param;
 
 	switch (msg) {
 	case WM_CLOSE:
@@ -1596,7 +1613,9 @@ PasswordDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lP)
 		break;
 
 	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_BIG, (LPARAM)hIcon);
 		SetWindowText(hDlg, passfile);
@@ -1667,7 +1686,7 @@ show_settings_dialog()
 	DLGTEMPLATE *dia = (DLGTEMPLATE *)mem;
 	WORD i, cl, nelems = 0;
 	short width, x, y;
-	static HWND sDlgHWnd;
+	static struct dlg_proc_param s_dlg_proc_param;
 
 	static struct {
 		DLGTEMPLATE template; /* 18 bytes */
@@ -1689,10 +1708,11 @@ show_settings_dialog()
 	                   8,
 	                   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 {
-		SetForegroundWindow(sDlgHWnd);
+		SetForegroundWindow(s_dlg_proc_param.hWnd);
 		return;
 	}
 
@@ -1823,9 +1843,12 @@ show_settings_dialog()
 	assert(((intptr_t)p - (intptr_t)mem) < (intptr_t)sizeof(mem));
 
 	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 WIDTH
@@ -1849,6 +1872,7 @@ change_password_file()
 	unsigned char mem[4096], *p;
 	DLGTEMPLATE *dia = (DLGTEMPLATE *)mem;
 	const char *domain = mg_get_option(g_ctx, "authentication_domain");
+	static struct dlg_proc_param s_dlg_proc_param;
 
 	static struct {
 		DLGTEMPLATE template; /* 18 bytes */
@@ -1870,9 +1894,11 @@ change_password_file()
 	                   8,
 	                   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 {
+		SetForegroundWindow(s_dlg_proc_param.hWnd);
 		return;
 	}
 
@@ -1885,7 +1911,7 @@ change_password_file()
 	of.Flags = OFN_CREATEPROMPT | OFN_NOCHANGEDIR | OFN_HIDEREADONLY;
 
 	if (IDOK != GetSaveFileName(&of)) {
-		sGuard--;
+		s_dlg_proc_param.guard = 0;
 		return;
 	}
 
@@ -1894,11 +1920,12 @@ change_password_file()
 		fclose(f);
 	} else {
 		MessageBox(NULL, path, "Can not open file", MB_ICONERROR);
-		sGuard--;
+		s_dlg_proc_param.guard = 0;
 		return;
 	}
 
 	do {
+		s_dlg_proc_param.hWnd = NULL;
 		(void)memset(mem, 0, sizeof(mem));
 		(void)memcpy(mem, &dialog_header, sizeof(dialog_header));
 		p = mem + sizeof(dialog_header);
@@ -1906,7 +1933,7 @@ change_password_file()
 		f = fopen(path, "r+");
 		if (!f) {
 			MessageBox(NULL, path, "Can not open file", MB_ICONERROR);
-			sGuard--;
+			s_dlg_proc_param.guard = 0;
 			return;
 		}
 
@@ -1943,7 +1970,7 @@ change_password_file()
 			            0x81,
 			            ID_CONTROLS + nelems + ID_FILE_BUTTONS_DELTA,
 			            WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL
-			                | WS_DISABLED,
+			                | ES_READONLY,
 			            245,
 			            y,
 			            60,
@@ -1954,7 +1981,7 @@ change_password_file()
 			            0x81,
 			            ID_CONTROLS + nelems,
 			            WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL
-			                | WS_DISABLED,
+			                | ES_READONLY,
 			            140,
 			            y,
 			            100,
@@ -2027,11 +2054,18 @@ change_password_file()
 		assert(((intptr_t)p - (intptr_t)mem) < (intptr_t)sizeof(mem));
 
 		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));
 
-	sGuard--;
+	s_dlg_proc_param.hWnd = NULL;
+	s_dlg_proc_param.guard = 0;
 
 #undef HEIGHT
 #undef WIDTH
@@ -2039,18 +2073,106 @@ change_password_file()
 }
 
 
-static void
+int
 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 {
-		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
 WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
 {
@@ -2289,6 +2412,7 @@ WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR cmdline, int show)
 	(void)show;
 
 	init_server_name((int)__argc, (const char **)__argv);
+	init_system_info();
 	memset(&cls, 0, sizeof(cls));
 	cls.lpfnWndProc = (WNDPROC)WindowProc;
 	cls.hIcon = LoadIcon(NULL, IDI_APPLICATION);
@@ -2334,6 +2458,8 @@ WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR cmdline, int show)
 		DispatchMessage(&msg);
 	}
 
+	free_system_info();
+
 	/* Return the WM_QUIT value. */
 	return (int)msg.wParam;
 }
@@ -2389,6 +2515,7 @@ int
 main(int argc, char *argv[])
 {
 	init_server_name(argc, (const char **)argv);
+	init_system_info();
 	start_civetweb(argc, argv);
 
 	[NSAutoreleasePool new];
@@ -2446,6 +2573,7 @@ main(int argc, char *argv[])
 	[NSApp run];
 
 	stop_civetweb();
+	free_system_info();
 
 	return EXIT_SUCCESS;
 }
@@ -2456,6 +2584,7 @@ int
 main(int argc, char *argv[])
 {
 	init_server_name(argc, (const char **)argv);
+	init_system_info();
 	start_civetweb(argc, argv);
 	fprintf(stdout,
 	        "%s started on port(s) %s with web root [%s]\n",
@@ -2472,6 +2601,8 @@ main(int argc, char *argv[])
 	stop_civetweb();
 	fprintf(stdout, "%s", " done.\n");
 
+	free_system_info();
+
 	return EXIT_SUCCESS;
 }
 #endif /* _WIN32 */

+ 1 - 1
src/mod_duktape.inl

@@ -1,6 +1,6 @@
 /* This file is part of the CivetWeb web server.
  * 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"

+ 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_private_lua.h"
 
@@ -785,7 +789,7 @@ lsp_url_decode(lua_State *L)
 		text = lua_tolstring(L, 1, &text_len);
 		is_form = (num_args == 2) ? lua_isboolean(L, 2) : 0;
 		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);
 		} else {
 			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, "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) {
 		/* reg_int64: content_length */
 		lua_pushstring(L, "content_length");
@@ -1883,6 +1891,90 @@ lua_websocket_close(struct mg_connection *conn, void *ws_arg)
 }
 #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
@@ -1896,6 +1988,7 @@ lua_init_optional_libraries(void)
 #endif
 }
 
+
 static 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
         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_settable(L, -3);
-            lua_pushnumber(L,newIndex);
+            lua_pushnumber(L, (lua_Number)newIndex);
             lua_gettable(L,-2);
         }
         else {
@@ -360,7 +360,7 @@ int Xml_eval(lua_State *L) {
         else break;
     }
     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);
         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
  * 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)
 #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
 timer_add(struct mg_context *ctx,
           double next_time,
@@ -30,16 +61,13 @@ timer_add(struct mg_context *ctx,
 {
 	unsigned u, v;
 	int error = 0;
-	struct timespec now;
-	double dt; /* double time */
+	double now;
 
 	if (ctx->stop_flag) {
 		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
 	 *        action will be called so fast as possible
@@ -49,19 +77,26 @@ timer_add(struct mg_context *ctx,
 	 *        then the period is working
 	 * Solution:
 	 *        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
 	*/
 	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);
 	if (ctx->timers->timer_count == MAX_TIMERS) {
 		error = 1;
 	} 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++) {
 			if (ctx->timers->timers[u].time > next_time) {
 				/* HCP24: moving all timers > next_time */
@@ -86,7 +121,6 @@ static void
 timer_thread_run(void *thread_func_param)
 {
 	struct mg_context *ctx = (struct mg_context *)thread_func_param;
-	struct timespec now;
 	double d;
 	unsigned u;
 	int re_schedule;
@@ -99,18 +133,12 @@ timer_thread_run(void *thread_func_param)
 		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) {
 		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];
 			for (u = 1; u < ctx->timers->timer_count; u++) {
 				ctx->timers->timers[u - 1] = ctx->timers->timers[u];
@@ -125,11 +153,20 @@ timer_thread_run(void *thread_func_param)
 		} else {
 			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
+
+		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);
 	(void)pthread_mutex_init(&ctx->timers->mutex, NULL);
 
+	(void)timer_getcurrenttime();
+
 	/* Start timer thread */
 	mg_start_thread_with_id(timer_thread, ctx, &ctx->timers->threadid);
 
@@ -166,6 +205,8 @@ static void
 timers_exit(struct mg_context *ctx)
 {
 	if (ctx->timers) {
+		pthread_mutex_lock(&ctx->timers->mutex);
+		ctx->timers->timer_count = 0;
 		(void)pthread_mutex_destroy(&ctx->timers->mutex);
 		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
 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
   DEPENDS c-library
   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
 civetweb_add_test(PublicServer "Check test environment")
+civetweb_add_test(PublicServer "Init library")
 civetweb_add_test(PublicServer "Start threads")
 civetweb_add_test(PublicServer "Start Stop HTTP 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")
 
 # Timer tests
-civetweb_add_test(Timer "Timer Periodic")
 civetweb_add_test(Timer "Timer Single Shot")
+civetweb_add_test(Timer "Timer Periodic")
+civetweb_add_test(Timer "Timer Mixed")
 
 # Tests with main.c
 #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
  * 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
  * 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
  * of this software and associated documentation files (the "Software"), to deal
@@ -346,37 +346,37 @@ END_TEST
 START_TEST(test_mg_vsnprintf)
 {
 	char buf[16];
-	int trunc;
+	int is_trunc;
 
 	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_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_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_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_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_int_eq(trunc, 1);
+	ck_assert_int_eq(is_trunc, 1);
 
 	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");
 }
 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
  * 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
  * 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
  * 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
  * 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
  * 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
  * 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
 	                                 * 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 */
 	r = mg_read(client, client_data_buf, sizeof(client_data_buf));
@@ -3547,12 +3556,22 @@ START_TEST(test_throttle)
 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 *
 make_public_server_suite(void)
 {
 	Suite *const suite = suite_create("PublicServer");
 
 	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_startstophttp = tcase_create("Start Stop HTTP 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);
 	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_set_timeout(tcase_startthreads, civetweb_min_test_timeout);
 	suite_add_tcase(suite, tcase_startthreads);
@@ -3624,6 +3647,10 @@ static int chk_failed = 0;
 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_threading(0);
 
@@ -3638,6 +3665,8 @@ MAIN_PUBLIC_SERVER(void)
 	test_error_log_file(0);
 	test_throttle(0);
 
+	mg_exit_library();
+
 	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
  * 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
  * 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
  * 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
  * of this software and associated documentation files (the "Software"), to deal
@@ -44,16 +44,35 @@
 
 #include "timertest.h"
 
+static int action_dec_ret;
 
 static int
-action1(void *arg)
+action_dec(void *arg)
 {
 	int *p = (int *)arg;
 	(*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(c, 0, sizeof(c));
 
+	action_dec_ret = 1;
+
 	mark_point();
 	timers_init(&ctx);
 	mg_sleep(100);
 	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 */
-    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);
 
 	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_le(c[0], +1);
 	ck_assert_int_ge(c[1], -1);
 	ck_assert_int_le(c[1], +1);
 	ck_assert_int_ge(c[2], -1);
 	ck_assert_int_le(c[2], +1);
-#endif
 }
 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;
 	int c[10];
 	memset(&ctx, 0, sizeof(ctx));
 	memset(c, 0, sizeof(c));
 
+	action_dec_ret = 1;
+
 	mark_point();
 	timers_init(&ctx);
 	mg_sleep(100);
 	mark_point();
 
 	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;
-	timer_add(&ctx, 0, 0.5, 1, action2, c + 2);
+	timer_add(&ctx, 0, 0, 1, action_dec, c + 2);
 	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 */
 
-    mark_point();
+	mark_point();
 	ctx.stop_flag = 99; /* End timer thread */
-    mark_point();
+	mark_point();
 
 	mg_sleep(1000); /* Sleep 1 second - timer will not run */
 
-    mark_point();
+	mark_point();
 
 	timers_exit(&ctx);
 
 	mark_point();
-    mg_sleep(100);
+	mg_sleep(100);
 
 	ck_assert_int_eq(c[0], 9);
 	ck_assert_int_eq(c[1], 4);
@@ -169,6 +217,78 @@ START_TEST(test_timer_oneshot)
 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 *
 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_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_set_timeout(tcase_timer_cyclic, 30);
 	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);
 	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;
 }
 
@@ -195,13 +321,24 @@ make_timertest_suite(void)
 void
 TIMER_PRIVATE(void)
 {
+	unsigned f_avail;
+	unsigned f_ret;
+
 #if defined(_WIN32)
 	WSADATA data;
 	WSAStartup(MAKEWORD(2, 2), &data);
 #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_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)
 	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;
-}