]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/jaegertracing/opentelemetry-cpp/third_party/prometheus-cpp/3rdparty/civetweb/src/mod_lua.inl
update ceph source to reef 18.1.2
[ceph.git] / ceph / src / jaegertracing / opentelemetry-cpp / third_party / prometheus-cpp / 3rdparty / civetweb / src / mod_lua.inl
diff --git a/ceph/src/jaegertracing/opentelemetry-cpp/third_party/prometheus-cpp/3rdparty/civetweb/src/mod_lua.inl b/ceph/src/jaegertracing/opentelemetry-cpp/third_party/prometheus-cpp/3rdparty/civetweb/src/mod_lua.inl
new file mode 100644 (file)
index 0000000..5cc9431
--- /dev/null
@@ -0,0 +1,3667 @@
+/* This file is part of the CivetWeb web server.
+ * See https://github.com/civetweb/civetweb/
+ */
+
+#if !defined(_WIN32)
+#include <dlfcn.h>
+#include <sys/mman.h>
+#endif
+
+#include "civetweb_lua.h"
+#include "civetweb_private_lua.h"
+
+
+#if defined(_WIN32)
+static void *
+mmap(void *addr, int64_t len, int prot, int flags, int fd, int offset)
+{
+       /* TODO (low): This is an incomplete implementation of mmap for windows.
+        * Currently it is sufficient, but there are a lot of unused parameters.
+        * Better use a function "mg_map" which only has the required parameters,
+        * and implement it using mmap in Linux and CreateFileMapping in Windows.
+        * No one should expect a full mmap for Windows here.
+        */
+       HANDLE fh = (HANDLE)_get_osfhandle(fd);
+       HANDLE mh = CreateFileMapping(fh, 0, PAGE_READONLY, 0, 0, 0);
+       void *p = MapViewOfFile(mh, FILE_MAP_READ, 0, 0, (size_t)len);
+       CloseHandle(mh);
+
+       /* unused parameters */
+       (void)addr;
+       (void)prot;
+       (void)flags;
+       (void)offset;
+
+       return p;
+}
+
+
+static void
+munmap(void *addr, int64_t length)
+{
+       /* unused parameters */
+       (void)length;
+
+       UnmapViewOfFile(addr);
+}
+
+
+#define MAP_PRIVATE (0)
+#define PROT_READ (0)
+#endif
+
+
+static const char *const LUASOCKET = "luasocket";
+static const char lua_regkey_ctx = 1;
+static const char lua_regkey_connlist = 2;
+static const char lua_regkey_lsp_include_history = 3;
+static const char lua_regkey_environment_type = 4;
+static const char lua_regkey_dtor = 5;
+
+
+/* Limit nesting depth of mg.include.
+ * This takes a lot of stack (~10 kB per recursion),
+ * so do not use a too high limit. */
+#if !defined(LSP_INCLUDE_MAX_DEPTH)
+#define LSP_INCLUDE_MAX_DEPTH (10)
+#endif
+
+
+/* Forward declarations */
+static int handle_lsp_request(struct mg_connection *,
+                              const char *,
+                              struct mg_file *,
+                              struct lua_State *);
+
+
+static void
+reg_lstring(struct lua_State *L,
+            const char *name,
+            const void *buffer,
+            size_t buflen)
+{
+       if (name != NULL && buffer != NULL) {
+               lua_pushstring(L, name);
+               lua_pushlstring(L, (const char *)buffer, buflen);
+               lua_rawset(L, -3);
+       }
+}
+
+
+static void
+reg_llstring(struct lua_State *L,
+             const void *buffer1,
+             size_t buflen1,
+             const void *buffer2,
+             size_t buflen2)
+{
+       if (buffer1 != NULL && buffer2 != NULL) {
+               lua_pushlstring(L, (const char *)buffer1, buflen1);
+               lua_pushlstring(L, (const char *)buffer2, buflen2);
+               lua_rawset(L, -3);
+       }
+}
+
+
+#define reg_string(L, name, val)                                               \
+       reg_lstring(L, name, val, (val != NULL) ? strlen(val) : 0)
+
+
+static void
+reg_int(struct lua_State *L, const char *name, int val)
+{
+       if (name != NULL) {
+               lua_pushstring(L, name);
+               lua_pushinteger(L, val);
+               lua_rawset(L, -3);
+       }
+}
+
+
+static void
+reg_i64(struct lua_State *L, const char *name, int64_t val)
+{
+       if (name == NULL) {
+               return;
+       }
+       lua_pushstring(L, name);
+       if (sizeof(lua_Integer) >= sizeof(val)) {
+               lua_pushinteger(L, (lua_Integer)val);
+       } else {
+               double d = (double)val;
+               lua_pushnumber(L, d);
+       }
+       lua_rawset(L, -3);
+}
+
+
+static void
+reg_double(struct lua_State *L, const char *name, double val)
+{
+       if (name != NULL) {
+               lua_pushstring(L, name);
+               lua_pushnumber(L, val);
+               lua_rawset(L, -3);
+       }
+}
+
+
+static void
+reg_boolean(struct lua_State *L, const char *name, int val)
+{
+       if (name != NULL) {
+               lua_pushstring(L, name);
+               lua_pushboolean(L, val != 0);
+               lua_rawset(L, -3);
+       }
+}
+
+
+static void
+reg_conn_function(struct lua_State *L,
+                  const char *name,
+                  lua_CFunction func,
+                  struct mg_connection *conn)
+{
+       if (name != NULL && func != NULL && conn != NULL) {
+               lua_pushstring(L, name);
+               lua_pushlightuserdata(L, conn);
+               lua_pushcclosure(L, func, 1);
+               lua_rawset(L, -3);
+       }
+}
+
+
+static void
+reg_function(struct lua_State *L, const char *name, lua_CFunction func)
+{
+       if (name != NULL && func != NULL) {
+               lua_pushstring(L, name);
+               lua_pushcclosure(L, func, 0);
+               lua_rawset(L, -3);
+       }
+}
+
+
+static void
+lua_cry(const struct mg_connection *conn,
+        int err,
+        lua_State *L,
+        const char *lua_title,
+        const char *lua_operation)
+{
+       DEBUG_TRACE("lua_cry (err=%i): %s: %s", err, lua_title, lua_operation);
+
+       switch (err) {
+       case LUA_OK:
+       case LUA_YIELD:
+               break;
+       case LUA_ERRRUN:
+               mg_cry_internal(conn,
+                               "%s: %s failed: runtime error: %s",
+                               lua_title,
+                               lua_operation,
+                               lua_tostring(L, -1));
+               break;
+       case LUA_ERRSYNTAX:
+               mg_cry_internal(conn,
+                               "%s: %s failed: syntax error: %s",
+                               lua_title,
+                               lua_operation,
+                               lua_tostring(L, -1));
+               break;
+       case LUA_ERRMEM:
+               mg_cry_internal(conn,
+                               "%s: %s failed: out of memory",
+                               lua_title,
+                               lua_operation);
+               break;
+#if LUA_VERSION_NUM < 504
+       /* LUA_ERRGCMM has been removed in Lua 5.4.
+        * See https://www.lua.org/manual/5.4/manual.html#8.3 */
+       case LUA_ERRGCMM:
+               mg_cry_internal(conn,
+                               "%s: %s failed: error during garbage collection",
+                               lua_title,
+                               lua_operation);
+               break;
+#endif
+       case LUA_ERRERR:
+               mg_cry_internal(conn,
+                               "%s: %s failed: error in error handling: %s",
+                               lua_title,
+                               lua_operation,
+                               lua_tostring(L, -1));
+               break;
+       default:
+               mg_cry_internal(
+                   conn, "%s: %s failed: error %i", lua_title, lua_operation, err);
+               break;
+       }
+}
+
+
+static int
+lsp_sock_close(lua_State *L)
+{
+       int num_args = lua_gettop(L);
+       size_t s;
+       SOCKET *psock;
+
+       if ((num_args == 1) && lua_istable(L, 1)) {
+               lua_getfield(L, -1, "sock");
+               psock = (SOCKET *)lua_tolstring(L, -1, &s);
+               if (s != sizeof(SOCKET)) {
+                       return luaL_error(L, "invalid internal state in :close() call");
+               }
+               /* Do not closesocket(*psock); here, close it in __gc */
+               (void)psock;
+       } else {
+               return luaL_error(L, "invalid :close() call");
+       }
+       return 0;
+}
+
+
+static int
+lsp_sock_recv(lua_State *L)
+{
+       int num_args = lua_gettop(L);
+       char buf[2000];
+       int n;
+       size_t s;
+       SOCKET *psock;
+
+       if ((num_args == 1) && lua_istable(L, 1)) {
+               lua_getfield(L, -1, "sock");
+               psock = (SOCKET *)lua_tolstring(L, -1, &s);
+               if (s != sizeof(SOCKET)) {
+                       return luaL_error(L, "invalid internal state in :recv() call");
+               }
+               n = recv(*psock, buf, sizeof(buf), 0);
+               if (n <= 0) {
+                       lua_pushnil(L);
+               } else {
+                       lua_pushlstring(L, buf, n);
+               }
+       } else {
+               return luaL_error(L, "invalid :recv() call");
+       }
+       return 1;
+}
+
+
+static int
+lsp_sock_send(lua_State *L)
+{
+       int num_args = lua_gettop(L);
+       const char *buf;
+       size_t len, sent = 0;
+       int n = 0;
+       size_t s;
+       SOCKET *psock;
+
+       if ((num_args == 2) && lua_istable(L, -2) && lua_isstring(L, -1)) {
+               buf = lua_tolstring(L, -1, &len);
+               lua_getfield(L, -2, "sock");
+               psock = (SOCKET *)lua_tolstring(L, -1, &s);
+               if (s != sizeof(SOCKET)) {
+                       return luaL_error(L, "invalid internal state in :close() call");
+               }
+
+               while (sent < len) {
+                       if ((n = send(*psock, buf + sent, (int)(len - sent), 0)) <= 0) {
+                               break;
+                       }
+                       sent += n;
+               }
+               lua_pushnumber(L, n);
+       } else {
+               return luaL_error(L, "invalid :close() call");
+       }
+       return 1;
+}
+
+
+static int
+lsp_sock_gc(lua_State *L)
+{
+       int num_args = lua_gettop(L);
+       size_t s;
+       SOCKET *psock;
+
+       if ((num_args == 1) && lua_istable(L, 1)) {
+               lua_getfield(L, -1, "sock");
+               psock = (SOCKET *)lua_tolstring(L, 1, &s);
+               if (s != sizeof(SOCKET)) {
+                       return luaL_error(
+                           L,
+                           "invalid internal state in __gc for object created by connect");
+               }
+               closesocket(*psock);
+       } else {
+               return luaL_error(L, "__gc for object created by connect failed");
+       }
+       return 0;
+}
+
+
+/* Methods and meta-methods supported by the object returned by connect.
+ * For meta-methods, see http://lua-users.org/wiki/MetatableEvents */
+static const struct luaL_Reg luasocket_methods[] = {{"close", lsp_sock_close},
+                                                    {"send", lsp_sock_send},
+                                                    {"recv", lsp_sock_recv},
+                                                    {"__gc", lsp_sock_gc},
+                                                    {NULL, NULL}};
+
+
+static int
+lsp_connect(lua_State *L)
+{
+       int num_args = lua_gettop(L);
+       char ebuf[100];
+       SOCKET sock;
+       union usa sa;
+       int ok;
+
+       if ((num_args == 3) && lua_isstring(L, 1) && lua_isnumber(L, 2)
+           && lua_isnumber(L, 3)) {
+
+               const char *host = lua_tostring(L, 1);
+               const int port = lua_tointeger(L, 2);
+               const int is_ssl = lua_tointeger(L, 3);
+
+               ok = connect_socket(
+                   NULL, host, port, is_ssl, ebuf, sizeof(ebuf), &sock, &sa);
+               if (!ok) {
+                       return luaL_error(L, ebuf);
+               } else {
+                       set_blocking_mode(sock);
+                       lua_newtable(L);
+                       reg_lstring(L, "sock", (const char *)&sock, sizeof(SOCKET));
+                       reg_string(L, "host", lua_tostring(L, -4));
+                       luaL_getmetatable(L, LUASOCKET);
+                       lua_setmetatable(L, -2);
+               }
+       } else {
+               return luaL_error(
+                   L, "connect(host,port,is_ssl): invalid parameter given.");
+       }
+       return 1;
+}
+
+
+static int
+lsp_error(lua_State *L)
+{
+       DEBUG_TRACE("%s", "lsp_error");
+       lua_getglobal(L, "mg");
+       lua_getfield(L, -1, "onerror");
+       lua_pushvalue(L, -3);
+       lua_pcall(L, 1, 0, 0);
+       return 0;
+}
+
+
+/* Silently stop processing chunks. */
+static void
+lsp_abort(lua_State *L)
+{
+       int top = lua_gettop(L);
+       DEBUG_TRACE("%s", "lsp_abort");
+       lua_getglobal(L, "mg");
+       lua_pushnil(L);
+       lua_setfield(L, -2, "onerror");
+       lua_settop(L, top);
+       lua_pushstring(L, "aborting");
+       lua_error(L);
+}
+
+
+struct lsp_var_reader_data {
+       int64_t len;
+       int64_t consumed;
+       const char *begin;
+       unsigned char state;
+       char tag;
+};
+
+
+/* Helper function to read the content of variable values */
+static const char *
+lsp_var_reader(lua_State *L, void *ud, size_t *sz)
+{
+       struct lsp_var_reader_data *reader = (struct lsp_var_reader_data *)ud;
+       const char *ret;
+       (void)(L); /* unused */
+
+       /* This reader is called multiple times, to fetch the full Lua script */
+       switch (reader->state) {
+       case 0:
+               /* First call: what function to call */
+               reader->consumed = 0;
+               ret = "mg.write(";
+               *sz = strlen(ret);
+               break;
+       case 1:
+               /* Second call: forward variable name */
+               ret = reader->begin;
+               *sz = (size_t)reader->len;
+               reader->consumed += reader->len;
+               break;
+       case 2:
+               /* Third call: close function call */
+               ret = ")";
+               *sz = strlen(ret);
+               break;
+       default:
+               /* Forth/Final call: tell Lua we got the entire script */
+               ret = 0;
+               *sz = 0;
+       }
+
+       /* Step to the next state for the next call */
+       reader->state++;
+       return ret;
+}
+
+
+static const char *
+lsp_kepler_reader(lua_State *L, void *ud, size_t *sz)
+{
+       struct lsp_var_reader_data *reader = (struct lsp_var_reader_data *)ud;
+       const char *ret;
+       int64_t i;
+       int64_t left;
+
+       (void)(L); /* unused */
+
+       /* This reader is called multiple times, to fetch the full Lua script */
+
+       if (reader->state == 0) {
+               /* First call: Send opening tag - what function to call */
+               ret = "mg.write([=======[";
+               *sz = strlen(ret);
+               reader->state = 1;
+               reader->consumed = 0;
+               return ret;
+       }
+
+       if (reader->state == 4) {
+               /* Final call: Tell Lua reader, we reached the end */
+               *sz = 0;
+               return 0;
+       }
+
+       left = reader->len - reader->consumed;
+       if (left == 0) {
+               /* We reached the end of the file/available data. */
+               /* Send closing tag. */
+               ret = "]=======]);\n";
+               *sz = strlen(ret);
+               reader->state = 4; /* Next will be the final call */
+               return ret;
+       }
+       if (left > MG_BUF_LEN / 100) {
+               left = MG_BUF_LEN / 100; /* TODO XXX */
+       }
+       i = 0;
+
+       if (reader->state == 1) {
+               /* State 1: plain text - put inside mg.write(...) */
+               for (;;) {
+                       /* Find next tag */
+                       while ((i < left) && (reader->begin[i + reader->consumed] != '<')) {
+                               i++;
+                       }
+                       if (i > 0) {
+                               /* Forward all data until the next tag */
+                               int64_t j = reader->consumed;
+                               reader->consumed += i;
+                               *sz = (size_t)i; /* cast is ok, i is limited to MG_BUF_LEN */
+                               return reader->begin + j;
+                       }
+
+                       /* assert (reader->begin[reader->state] == '<') */
+                       /* assert (i == 0) */
+                       if (0 == memcmp(reader->begin + reader->consumed, "<?lua", 5)) {
+                               /* kepler <?lua syntax */
+                               i = 5;
+                               reader->tag = '?';
+                               break;
+                       } else if (0 == memcmp(reader->begin + reader->consumed, "<%", 2)) {
+                               /* kepler <% syntax */
+                               i = 2;
+                               reader->tag = '%';
+                               break;
+                       } else if (0 == memcmp(reader->begin + reader->consumed, "<?", 2)) {
+                               /* civetweb <? syntax */
+                               i = 2;
+                               reader->tag = '?';
+                               break;
+                       } else {
+                               i = 1;
+                       }
+               }
+               /* We found an opening or closing tag, or we reached the end of the
+                * file/data block */
+               if (reader->begin[reader->consumed + i] == '=') {
+                       /* Lua= tag - Lua expression to print */
+                       ret = "]=======]);\nmg.write(";
+                       reader->state = 3;
+                       i++;
+               } else {
+                       /* Normal Lua tag - Lua chunk */
+                       ret = "]=======]);\n";
+                       reader->state = 2;
+               }
+               *sz = strlen(ret);
+               reader->consumed += i; /* length of <?lua or <% tag */
+               return ret;
+       }
+
+       if ((reader->state == 2) || (reader->state == 3)) {
+               /* State 2: Lua chunkg - keep outside mg.write(...) */
+               /* State 3: Lua expression - inside mg.write(...) */
+
+               for (;;) {
+                       int close_tag_found = 0;
+
+                       /* Find end tag */
+                       while ((i < left)
+                              && (reader->begin[i + reader->consumed] != reader->tag)) {
+                               i++;
+                       }
+                       if (i > 0) {
+                               /* Forward all data inside the Lua script tag */
+                               int64_t j = reader->consumed;
+                               reader->consumed += i;
+                               *sz = (size_t)i; /* cast is ok, i is limited to MG_BUF_LEN */
+
+                               return reader->begin + j;
+                       }
+
+                       /* Is this the closing tag we are looking for? */
+                       close_tag_found =
+                           ((i + 1 < left)
+                            && (reader->begin[i + 1 + reader->consumed] == '>'));
+
+                       if (close_tag_found) {
+                               /* Drop close tag */
+                               reader->consumed += 2;
+
+                               if (reader->state == 2) {
+                                       /* Send a new opening tag to Lua */
+                                       ret = ";\nmg.write([=======[";
+                               } else {
+                                       ret = ");\nmg.write([=======[";
+                               }
+                               *sz = strlen(ret);
+                               reader->state = 1;
+                               return ret;
+                       } else {
+                               /* Not a close tag, continue searching */
+                               i++;
+                       }
+               }
+       }
+
+
+       /* Must never be reached */
+       *sz = 0;
+       return 0;
+}
+
+
+static int
+run_lsp_kepler(struct mg_connection *conn,
+               const char *path,
+               const char *p,
+               int64_t len,
+               lua_State *L,
+               int depth)
+{
+
+       int lua_ok;
+       struct lsp_var_reader_data data;
+       char date[64];
+       time_t curtime = time(NULL);
+
+       gmt_time_string(date, sizeof(date), &curtime);
+
+       if (depth == 1) {
+               /* Top level page assumes keep_alive is disabled.
+                * Do not overwrite this setting for included pages. */
+               conn->must_close = 1;
+
+               /* Only send a HTML header, if this is the top level page.
+                * If this page is included by some mg.include calls, do not add a
+                * header. */
+               mg_printf(conn, "HTTP/1.1 200 OK\r\n");
+               send_no_cache_header(conn);
+               send_additional_header(conn);
+               mg_printf(conn,
+                         "Date: %s\r\n"
+                         "Connection: close\r\n"
+                         "Content-Type: text/html; charset=utf-8\r\n\r\n",
+                         date);
+       }
+
+       data.begin = p;
+       data.len = len;
+       data.state = 0;
+       data.consumed = 0;
+       data.tag = 0;
+       lua_ok = mg_lua_load(L, lsp_kepler_reader, &data, path, NULL);
+
+       if (lua_ok) {
+               /* Syntax error or OOM.
+                * Error message is pushed on stack. */
+               lua_pcall(L, 1, 0, 0);
+               lua_cry(conn, lua_ok, L, "LSP", "execute"); /* XXX TODO: everywhere ! */
+
+       } else {
+               /* Success loading chunk. Call it. */
+               lua_pcall(L, 0, 0, 1);
+       }
+       return 0;
+}
+
+
+static int
+run_lsp_civetweb(struct mg_connection *conn,
+                 const char *path,
+                 const char *p,
+                 int64_t len,
+                 lua_State *L,
+                 int depth)
+{
+       int i, j, s, pos = 0, lines = 1, lualines = 0, is_var, lua_ok;
+       char chunkname[MG_BUF_LEN];
+       struct lsp_var_reader_data data;
+       const char lsp_mark1 = '?'; /* Use <? code ?> */
+       const char lsp_mark2 = '%'; /* Use <% code %> */
+
+       if (depth == 1) {
+               /* Assume the script does not support keep_alive. The script may change
+                * this by calling mg.keep_alive(true). */
+               conn->must_close = 1;
+       }
+
+       for (i = 0; i < len; i++) {
+               if (p[i] == '\n') {
+                       lines++;
+               }
+
+               /* Lua pages are normal text, unless there is a "<?" or "<%" tag. */
+               if (((i + 1) < len) && (p[i] == '<')
+                   && ((p[i + 1] == lsp_mark1) || (p[i + 1] == lsp_mark2))) {
+
+                       /* Opening tag way "<?" or "<%", closing tag must be the same. */
+                       char lsp_mark_used = p[i + 1];
+
+                       /* <?= var ?> or <%= var %> means a variable is enclosed and its
+                        * value should be printed */
+                       if (0 == memcmp("lua", p + i + 2, 3)) {
+                               /* Syntax: <?lua code ?> or <?lua= var ?> */
+                               /* This is added for compatibility to other LSP syntax
+                                * definitions. */
+                               /* Skip 3 letters ("lua"). */
+                               s = 3;
+                       } else {
+                               /* no additional letters to skip, only "<?" */
+                               s = 0;
+                       }
+
+                       /* Check for '=' in "<?= ..." or "<%= ..." or "<?lua= ..." */
+                       is_var = (((i + s + 2) < len) && (p[i + s + 2] == '='));
+                       if (is_var) {
+                               /* use variable value (print it later) */
+                               j = i + 2;
+                       } else {
+                               /* execute script code */
+                               j = i + 1;
+                       }
+
+                       while (j < len) {
+
+                               if (p[j] == '\n') {
+                                       /* Add line (for line number offset) */
+                                       lualines++;
+                               }
+
+                               /* Check for closing tag. */
+                               if (((j + 1) < len) && (p[j] == lsp_mark_used)
+                                   && (p[j + 1] == '>')) {
+                                       /* We found the closing tag of the Lua tag. */
+
+                                       /* Print everything before the Lua opening tag. */
+                                       mg_write(conn, p + pos, i - pos);
+
+                                       /* Set a name for debugging purposes */
+                                       mg_snprintf(conn,
+                                                   NULL, /* ignore truncation for debugging */
+                                                   chunkname,
+                                                   sizeof(chunkname),
+                                                   "@%s+%i",
+                                                   path,
+                                                   lines);
+
+                                       /* Prepare data for Lua C functions */
+                                       lua_pushlightuserdata(L, conn);
+                                       lua_pushcclosure(L, lsp_error, 1);
+
+                                       /* Distinguish between <? script ?> (is_var == 0)
+                                        * and <?= expression ?> (is_var != 0). */
+                                       if (is_var) {
+                                               /* For variables: Print the value */
+                                               /* Note: <?= expression ?> is equivalent to
+                                                * <? mg.write( expression ) ?> */
+                                               data.begin = p + (i + 3 + s);
+                                               data.len = j - (i + 3 + s);
+                                               data.state = 0;
+                                               data.consumed = 0;
+                                               data.tag = 0;
+                                               lua_ok = mg_lua_load(
+                                                   L, lsp_var_reader, &data, chunkname, NULL);
+                                       } else {
+                                               /* For scripts: Execute them */
+                                               lua_ok = luaL_loadbuffer(L,
+                                                                        p + (i + 2 + s),
+                                                                        j - (i + 2 + s),
+                                                                        chunkname);
+                                       }
+
+                                       if (lua_ok) {
+                                               /* Syntax error or OOM.
+                                                * Error message is pushed on stack. */
+                                               lua_pcall(L, 1, 0, 0);
+                                       } else {
+                                               /* Success loading chunk. Call it. */
+                                               lua_pcall(L, 0, 0, 1);
+                                       }
+
+                                       /* Progress until after the Lua closing tag. */
+                                       pos = j + 2;
+                                       i = pos - 1;
+                                       break;
+                               }
+                               j++;
+                       }
+
+                       /* Line number for debugging/error logging. */
+                       if (lualines > 0) {
+                               lines += lualines;
+                               lualines = 0;
+                       }
+               }
+       }
+
+       /* Print everything after the last Lua closing tag. */
+       if (i > pos) {
+               mg_write(conn, p + pos, i - pos);
+       }
+
+       return 0;
+}
+
+
+/* mg.write: Send data to the client */
+static int
+lsp_write(lua_State *L)
+{
+       struct mg_connection *conn =
+           (struct mg_connection *)lua_touserdata(L, lua_upvalueindex(1));
+       int num_args = lua_gettop(L);
+       const char *str;
+       size_t size;
+       int i;
+       int rv = 1;
+
+       for (i = 1; i <= num_args; i++) {
+               if (lua_isstring(L, i)) {
+                       str = lua_tolstring(L, i, &size);
+                       if (mg_write(conn, str, size) != (int)size) {
+                               rv = 0;
+                       }
+               }
+       }
+       lua_pushboolean(L, rv);
+
+       return 1;
+}
+
+
+/* mg.read: Read data from the client (e.g., from a POST request) */
+static int
+lsp_read(lua_State *L)
+{
+       struct mg_connection *conn =
+           (struct mg_connection *)lua_touserdata(L, lua_upvalueindex(1));
+       char buf[1024];
+       int len = mg_read(conn, buf, sizeof(buf));
+
+       if (len <= 0)
+               return 0;
+       lua_pushlstring(L, buf, len);
+
+       return 1;
+}
+
+
+/* mg.keep_alive: Allow Lua pages to use the http keep-alive mechanism */
+static int
+lsp_keep_alive(lua_State *L)
+{
+       struct mg_connection *conn =
+           (struct mg_connection *)lua_touserdata(L, lua_upvalueindex(1));
+       int num_args = lua_gettop(L);
+
+       /* This function may be called with one parameter (boolean) to set the
+       keep_alive state.
+       Or without a parameter to just query the current keep_alive state. */
+       if ((num_args == 1) && lua_isboolean(L, 1)) {
+               conn->must_close = !lua_toboolean(L, 1);
+       } else if (num_args != 0) {
+               /* Syntax error */
+               return luaL_error(L, "invalid keep_alive() call");
+       }
+
+       /* Return the current "keep_alive" state. This may be false, even it
+        * keep_alive(true) has been called. */
+       lua_pushboolean(L, should_keep_alive(conn));
+       return 1;
+}
+
+
+/* Stack of includes */
+struct lsp_include_history {
+       int depth;
+       const char *script[LSP_INCLUDE_MAX_DEPTH + 1];
+};
+
+
+/* mg.include: Include another .lp file */
+static int
+lsp_include(lua_State *L)
+{
+       struct mg_connection *conn =
+           (struct mg_connection *)lua_touserdata(L, lua_upvalueindex(1));
+       int num_args = lua_gettop(L);
+       struct mg_file file = STRUCT_FILE_INITIALIZER;
+       const char *file_name = (num_args >= 1) ? lua_tostring(L, 1) : NULL;
+       const char *path_type = (num_args >= 2) ? lua_tostring(L, 2) : NULL;
+       struct lsp_include_history *include_history;
+
+       if (path_type == NULL) {
+               /* default to "absolute" */
+               path_type = "a";
+       }
+
+       if ((file_name != NULL) && (num_args <= 2)) {
+
+               lua_pushlightuserdata(L, (void *)&lua_regkey_lsp_include_history);
+               lua_gettable(L, LUA_REGISTRYINDEX);
+               include_history = (struct lsp_include_history *)lua_touserdata(L, -1);
+
+               if (include_history->depth >= ((int)(LSP_INCLUDE_MAX_DEPTH))) {
+                       mg_cry_internal(
+                           conn,
+                           "lsp max include depth of %i reached while including %s",
+                           (int)(LSP_INCLUDE_MAX_DEPTH),
+                           file_name);
+               } else {
+                       char file_name_path[512];
+                       char *p;
+                       size_t len;
+                       int truncated = 0;
+
+                       file_name_path[511] = 0;
+
+                       if (*path_type == 'v') {
+                               /* "virtual" = relative to document root. */
+                               (void)mg_snprintf(conn,
+                                                 &truncated,
+                                                 file_name_path,
+                                                 sizeof(file_name_path),
+                                                 "%s/%s",
+                                                 conn->dom_ctx->config[DOCUMENT_ROOT],
+                                                 file_name);
+
+                       } else if (*path_type == 'a') {
+                               /* "absolute" = file name is relative to the
+                                * webserver working directory
+                                * or it is absolute system path. */
+                               /* path_type==NULL is the legacy use case with 1 argument */
+                               (void)mg_snprintf(conn,
+                                                 &truncated,
+                                                 file_name_path,
+                                                 sizeof(file_name_path),
+                                                 "%s",
+                                                 file_name);
+
+                       } else if ((*path_type == 'r') || (*path_type == 'f')) {
+                               /* "relative" = file name is relative to the
+                                * currect document */
+                               (void)mg_snprintf(
+                                   conn,
+                                   &truncated,
+                                   file_name_path,
+                                   sizeof(file_name_path),
+                                   "%s",
+                                   include_history->script[include_history->depth]);
+
+                               if (!truncated) {
+                                       if ((p = strrchr(file_name_path, '/')) != NULL) {
+                                               p[1] = '\0';
+                                       }
+                                       len = strlen(file_name_path);
+                                       (void)mg_snprintf(conn,
+                                                         &truncated,
+                                                         file_name_path + len,
+                                                         sizeof(file_name_path) - len,
+                                                         "%s",
+                                                         file_name);
+                               }
+
+                       } else {
+                               return luaL_error(
+                                   L,
+                                   "invalid path_type in include(file_name, path_type) call");
+                       }
+
+                       if (handle_lsp_request(conn, file_name_path, &file, L)) {
+                               /* handle_lsp_request returned an error code, meaning an error
+                                * occurred in the included page and mg.onerror returned
+                                * non-zero.
+                                * Stop processing.
+                                */
+
+                               lsp_abort(L);
+                       }
+               }
+
+       } else {
+               /* Syntax error */
+               return luaL_error(L, "invalid include() call");
+       }
+       return 0;
+}
+
+
+/* mg.cry: Log an error. Default value for mg.onerror. */
+static int
+lsp_cry(lua_State *L)
+{
+       struct mg_connection *conn =
+           (struct mg_connection *)lua_touserdata(L, lua_upvalueindex(1));
+       int num_args = lua_gettop(L);
+       const char *text = (num_args == 1) ? lua_tostring(L, 1) : NULL;
+
+       if (text) {
+               mg_cry_internal(conn, "%s", lua_tostring(L, -1));
+       } else {
+               /* Syntax error */
+               return luaL_error(L, "invalid cry() call");
+       }
+       return 0;
+}
+
+
+/* mg.redirect: Redirect the request (internally). */
+static int
+lsp_redirect(lua_State *L)
+{
+       struct mg_connection *conn =
+           (struct mg_connection *)lua_touserdata(L, lua_upvalueindex(1));
+       int num_args = lua_gettop(L);
+       const char *target = (num_args == 1) ? lua_tostring(L, 1) : NULL;
+
+       if (target) {
+               conn->request_info.local_uri = target;
+               handle_request(conn);
+               lsp_abort(L);
+       } else {
+               /* Syntax error */
+               return luaL_error(L, "invalid redirect() call");
+       }
+       return 0;
+}
+
+
+/* mg.send_file */
+static int
+lsp_send_file(lua_State *L)
+{
+       struct mg_connection *conn =
+           (struct mg_connection *)lua_touserdata(L, lua_upvalueindex(1));
+       int num_args = lua_gettop(L);
+       const char *filename = (num_args == 1) ? lua_tostring(L, 1) : NULL;
+
+       if (filename) {
+               mg_send_file(conn, filename);
+       } else {
+               /* Syntax error */
+               return luaL_error(L, "invalid send_file() call");
+       }
+       return 0;
+}
+
+
+/* mg.mg_send_file_body */
+static int
+lsp_send_file_body(lua_State *L)
+{
+       struct mg_connection *conn =
+           (struct mg_connection *)lua_touserdata(L, lua_upvalueindex(1));
+       int num_args = lua_gettop(L);
+       const char *filename = (num_args == 1) ? lua_tostring(L, 1) : NULL;
+       int ret;
+
+       if (filename) {
+               ret = mg_send_file_body(conn, filename);
+       } else {
+               /* Syntax error */
+               return luaL_error(L, "invalid send_file_body() call");
+       }
+
+       lua_pushboolean(L, ret >= 0);
+       return 1;
+}
+
+
+/* mg.send_http_error */
+static int
+lsp_send_http_error(lua_State *L)
+{
+       struct mg_connection *conn =
+           (struct mg_connection *)lua_touserdata(L, lua_upvalueindex(1));
+       int num_args = lua_gettop(L);
+       int status = (num_args >= 1) ? (int)lua_tonumber(L, 1) : -1;
+       const char *auxText = (num_args >= 2) ? lua_tostring(L, 2) : NULL;
+       int ret;
+
+       if ((status >= 100) && (status <= 999)) {
+               ret = mg_send_http_error(conn,
+                                        status,
+                                        "%s",
+                                        (auxText != NULL) ? auxText : "");
+       } else {
+               /* Syntax error */
+               return luaL_error(L, "invalid send_http_error() call");
+       }
+
+       lua_pushnumber(L, ret);
+       return 1;
+}
+
+
+/* mg.send_http_ok */
+static int
+lsp_send_http_ok(lua_State *L)
+{
+       struct mg_connection *conn =
+           (struct mg_connection *)lua_touserdata(L, lua_upvalueindex(1));
+       int num_args = lua_gettop(L);
+       int type1, type2;
+       const char *content_type = NULL;
+       const char *content = NULL;
+       int64_t content_len = 0;
+       int ret;
+
+       if (num_args < 2) {
+               /* Syntax error */
+               return luaL_error(L, "invalid send_http_ok() call");
+       }
+       type1 = lua_type(L, 1);
+       type2 = lua_type(L, 2);
+       if (type1 == LUA_TSTRING) {
+               content_type = lua_tostring(L, 1);
+       } else if (type1 != LUA_TNIL) {
+               /* Syntax error */
+               return luaL_error(L, "invalid send_http_ok() call");
+       }
+       if (type2 == LUA_TSTRING) {
+               size_t len;
+               content = lua_tolstring(L, 2, &len);
+               content_len = (int64_t)len;
+       } else if (type2 == LUA_TNUMBER) {
+               content_len = (int64_t)lua_tonumber(L, 2);
+       } else {
+               /* Syntax error */
+               return luaL_error(L, "invalid send_http_ok() call");
+       }
+
+       ret = mg_send_http_ok(conn, content_type, content_len);
+
+       if ((ret == 0) && (content != NULL) && (content_len > 0)) {
+               mg_write(conn, content, (size_t)content_len);
+       }
+
+       lua_pushnumber(L, ret);
+       return 1;
+}
+
+
+/* mg.mg_send_http_redirect */
+static int
+lsp_send_http_redirect(lua_State *L)
+{
+       struct mg_connection *conn =
+           (struct mg_connection *)lua_touserdata(L, lua_upvalueindex(1));
+       int num_args = lua_gettop(L);
+       int type1, type2;
+       const char *target_url = NULL;
+       int redirect_code = 300;
+       int ret;
+
+       if (num_args < 2) {
+               /* Syntax error */
+               return luaL_error(L, "invalid send_http_redirect() call");
+       }
+       type1 = lua_type(L, 1);
+       type2 = lua_type(L, 2);
+       if (type1 == LUA_TSTRING) {
+               target_url = lua_tostring(L, 1);
+       } else if (type1 != LUA_TNIL) {
+               /* Syntax error */
+               return luaL_error(L, "invalid send_http_redirect() call");
+       }
+       if (type2 == LUA_TNUMBER) {
+               redirect_code = (int)lua_tonumber(L, 2);
+       } else {
+               /* Syntax error */
+               return luaL_error(L, "invalid send_http_redirect() call");
+       }
+
+       ret = mg_send_http_redirect(conn, target_url, redirect_code);
+
+       lua_pushnumber(L, ret);
+       return 1;
+}
+
+
+/* mg.get_time */
+static int
+lsp_get_time(lua_State *L)
+{
+       int num_args = lua_gettop(L);
+       int monotonic = (num_args > 0) ? lua_toboolean(L, 1) : 0;
+       struct timespec ts;
+       double d;
+
+       clock_gettime(monotonic ? CLOCK_MONOTONIC : CLOCK_REALTIME, &ts);
+       d = (double)ts.tv_sec + ((double)ts.tv_nsec * 1.0E-9);
+       lua_pushnumber(L, d);
+       return 1;
+}
+
+
+/* mg.get_var */
+static int
+lsp_get_var(lua_State *L)
+{
+       int num_args = lua_gettop(L);
+       const char *data, *var_name;
+       size_t data_len, occurrence;
+       int ret;
+       struct mg_context *ctx;
+
+       lua_pushlightuserdata(L, (void *)&lua_regkey_ctx);
+       lua_gettable(L, LUA_REGISTRYINDEX);
+       ctx = (struct mg_context *)lua_touserdata(L, -1);
+
+       if ((num_args >= 2) && (num_args <= 3)) {
+               char *dst;
+               data = lua_tolstring(L, 1, &data_len);
+               var_name = lua_tostring(L, 2);
+               occurrence = (num_args > 2) ? (long)lua_tonumber(L, 3) : 0;
+
+               /* Allocate dynamically, so there is no internal limit for get_var */
+               dst = (char *)mg_malloc_ctx(data_len + 1, ctx);
+               if (!dst) {
+                       return luaL_error(L, "out of memory in get_var() call");
+               }
+
+               ret = mg_get_var2(data, data_len, var_name, dst, data_len, occurrence);
+               if (ret >= 0) {
+                       /* Variable found: return value to Lua */
+                       lua_pushstring(L, dst);
+               } else {
+                       /* Variable not found */
+                       lua_pushnil(L);
+               }
+               mg_free(dst);
+       } else {
+               /* Syntax error */
+               return luaL_error(L, "invalid get_var() call");
+       }
+       return 1;
+}
+
+
+#define MG_MAX_FORM_FIELDS (64)
+
+/* mg.split_form_data */
+static int
+lsp_split_form_urlencoded(lua_State *L)
+{
+       int num_args = lua_gettop(L);
+       const char *in;
+       size_t len;
+       char *buf;
+       struct mg_context *ctx;
+
+       struct mg_header form_fields[MG_MAX_FORM_FIELDS] = {0};
+       int ret, i;
+
+       if (num_args != 1) {
+               return luaL_error(L, "invalid split_form_data() call");
+       }
+
+       lua_pushlightuserdata(L, (void *)&lua_regkey_ctx);
+       lua_gettable(L, LUA_REGISTRYINDEX);
+       ctx = (struct mg_context *)lua_touserdata(L, -1);
+
+       /* Get input (const string) */
+       in = lua_tolstring(L, 1, &len);
+
+       /* Create a modifyable copy */
+       buf = (char *)mg_malloc_ctx(len + 1, ctx);
+       if (buf == NULL) {
+               return luaL_error(L, "out of memory in invalid split_form_data() call");
+       }
+       memcpy(buf, in, len + 1);
+
+       /* mg_split_form_urlencoded does the real work */
+       ret = mg_split_form_urlencoded(buf, form_fields, MG_MAX_FORM_FIELDS);
+
+       if (ret < 0) {
+               return luaL_error(L, "error in invalid split_form_data() call");
+       }
+
+       /* return a table */
+       lua_newtable(L);
+       for (i = 0; i < ret; i++) {
+
+               lua_newtable(L);
+               if (form_fields[i].name) {
+                       lua_pushstring(L, form_fields[i].name);
+               } else {
+                       lua_pushnil(L);
+               }
+               lua_setfield(L, -2, "name");
+               if (form_fields[i].value) {
+                       lua_pushstring(L, form_fields[i].value);
+               } else {
+                       lua_pushnil(L);
+               }
+               lua_setfield(L, -2, "value");
+
+               lua_rawseti(L, -2, i + 1);
+       }
+
+       mg_free(buf);
+
+       return 1;
+}
+
+
+/* mg.get_mime_type */
+static int
+lsp_get_mime_type(lua_State *L)
+{
+       int num_args = lua_gettop(L);
+       struct vec mime_type = {0, 0};
+       const char *text;
+
+       struct mg_connection *conn =
+           (struct mg_connection *)lua_touserdata(L, lua_upvalueindex(1));
+
+       if (num_args == 1) {
+               text = lua_tostring(L, 1);
+               if (text) {
+                       if (conn) {
+                               get_mime_type(conn, text, &mime_type);
+                               lua_pushlstring(L, mime_type.ptr, mime_type.len);
+                       } else {
+                               text = mg_get_builtin_mime_type(text);
+                               lua_pushstring(L, text);
+                       }
+               } else {
+                       /* Syntax error */
+                       return luaL_error(L, "invalid argument for get_mime_type() call");
+               }
+       } else {
+               /* Syntax error */
+               return luaL_error(L, "invalid get_mime_type() call");
+       }
+       return 1;
+}
+
+
+/* mg.get_cookie */
+static int
+lsp_get_cookie(lua_State *L)
+{
+       int num_args = lua_gettop(L);
+       const char *cookie;
+       const char *var_name;
+       int ret;
+       struct mg_context *ctx;
+
+       lua_pushlightuserdata(L, (void *)&lua_regkey_ctx);
+       lua_gettable(L, LUA_REGISTRYINDEX);
+       ctx = (struct mg_context *)lua_touserdata(L, -1);
+
+       if (num_args == 2) {
+               /* Correct number of arguments */
+               size_t data_len;
+               char *dst;
+
+               cookie = lua_tolstring(L, 1, &data_len);
+               var_name = lua_tostring(L, 2);
+
+               if (cookie == NULL || var_name == NULL) {
+                       /* Syntax error */
+                       return luaL_error(L, "invalid get_cookie() call");
+               }
+
+               dst = (char *)mg_malloc_ctx(data_len + 1, ctx);
+               if (!dst) {
+                       return luaL_error(L, "out of memory in get_cookie() call");
+               }
+
+               ret = mg_get_cookie(cookie, var_name, dst, data_len);
+
+               if (ret >= 0) {
+                       lua_pushlstring(L, dst, ret);
+               } else {
+                       lua_pushnil(L);
+               }
+               mg_free(dst);
+
+       } else {
+               /* Syntax error */
+               return luaL_error(L, "invalid get_cookie() call");
+       }
+       return 1;
+}
+
+
+/* mg.md5 */
+static int
+lsp_md5(lua_State *L)
+{
+       int num_args = lua_gettop(L);
+       const char *text;
+       md5_byte_t hash[16];
+       md5_state_t ctx;
+       size_t text_len;
+       char buf[40];
+
+       if (num_args == 1) {
+               text = lua_tolstring(L, 1, &text_len);
+               if (text) {
+                       md5_init(&ctx);
+                       md5_append(&ctx, (const md5_byte_t *)text, text_len);
+                       md5_finish(&ctx, hash);
+                       bin2str(buf, hash, sizeof(hash));
+                       lua_pushstring(L, buf);
+               } else {
+                       lua_pushnil(L);
+               }
+       } else {
+               /* Syntax error */
+               return luaL_error(L, "invalid md5() call");
+       }
+       return 1;
+}
+
+
+/* mg.url_encode */
+static int
+lsp_url_encode(lua_State *L)
+{
+       int num_args = lua_gettop(L);
+       const char *text;
+       size_t text_len;
+       char *dst;
+       int dst_len;
+       struct mg_context *ctx;
+
+       lua_pushlightuserdata(L, (void *)&lua_regkey_ctx);
+       lua_gettable(L, LUA_REGISTRYINDEX);
+       ctx = (struct mg_context *)lua_touserdata(L, -1);
+
+       if (num_args == 1) {
+               text = lua_tolstring(L, 1, &text_len);
+               if (text) {
+                       dst_len = 3 * (int)text_len + 1;
+                       dst = ((text_len < 0x2AAAAAAA) ? (char *)mg_malloc_ctx(dst_len, ctx)
+                                                      : (char *)NULL);
+                       if (dst) {
+                               mg_url_encode(text, dst, dst_len);
+                               lua_pushstring(L, dst);
+                               mg_free(dst);
+                       } else {
+                               return luaL_error(L, "out of memory in url_encode() call");
+                       }
+               } else {
+                       lua_pushnil(L);
+               }
+       } else {
+               /* Syntax error */
+               return luaL_error(L, "invalid url_encode() call");
+       }
+       return 1;
+}
+
+
+/* mg.url_decode */
+static int
+lsp_url_decode(lua_State *L)
+{
+       int num_args = lua_gettop(L);
+       const char *text;
+       size_t text_len;
+       int is_form;
+       char *dst;
+       int dst_len;
+       struct mg_context *ctx;
+
+       lua_pushlightuserdata(L, (void *)&lua_regkey_ctx);
+       lua_gettable(L, LUA_REGISTRYINDEX);
+       ctx = (struct mg_context *)lua_touserdata(L, -1);
+
+       if (num_args == 1 || (num_args == 2 && lua_isboolean(L, 2))) {
+               text = lua_tolstring(L, 1, &text_len);
+               is_form = (num_args == 2) ? lua_isboolean(L, 2) : 0;
+               if (text) {
+                       dst_len = (int)text_len + 1;
+                       dst = ((text_len < 0x7FFFFFFF) ? (char *)mg_malloc_ctx(dst_len, ctx)
+                                                      : (char *)NULL);
+                       if (dst) {
+                               mg_url_decode(text, (int)text_len, dst, dst_len, is_form);
+                               lua_pushstring(L, dst);
+                               mg_free(dst);
+                       } else {
+                               return luaL_error(L, "out of memory in url_decode() call");
+                       }
+               } else {
+                       lua_pushnil(L);
+               }
+       } else {
+               /* Syntax error */
+               return luaL_error(L, "invalid url_decode() call");
+       }
+       return 1;
+}
+
+
+/* mg.base64_encode */
+static int
+lsp_base64_encode(lua_State *L)
+{
+       int num_args = lua_gettop(L);
+       const char *text;
+       size_t text_len;
+       char *dst;
+       struct mg_context *ctx;
+
+       lua_pushlightuserdata(L, (void *)&lua_regkey_ctx);
+       lua_gettable(L, LUA_REGISTRYINDEX);
+       ctx = (struct mg_context *)lua_touserdata(L, -1);
+
+       if (num_args == 1) {
+               text = lua_tolstring(L, 1, &text_len);
+               if (text) {
+                       dst = (char *)mg_malloc_ctx(text_len * 8 / 6 + 4, ctx);
+                       if (dst) {
+                               base64_encode((const unsigned char *)text, (int)text_len, dst);
+                               lua_pushstring(L, dst);
+                               mg_free(dst);
+                       } else {
+                               return luaL_error(L, "out of memory in base64_encode() call");
+                       }
+               } else {
+                       lua_pushnil(L);
+               }
+       } else {
+               /* Syntax error */
+               return luaL_error(L, "invalid base64_encode() call");
+       }
+       return 1;
+}
+
+
+/* mg.base64_encode */
+static int
+lsp_base64_decode(lua_State *L)
+{
+       int num_args = lua_gettop(L);
+       const char *text;
+       size_t text_len, dst_len;
+       int ret;
+       char *dst;
+       struct mg_context *ctx;
+
+       lua_pushlightuserdata(L, (void *)&lua_regkey_ctx);
+       lua_gettable(L, LUA_REGISTRYINDEX);
+       ctx = (struct mg_context *)lua_touserdata(L, -1);
+
+       if (num_args == 1) {
+               text = lua_tolstring(L, 1, &text_len);
+               if (text) {
+                       dst = (char *)mg_malloc_ctx(text_len, ctx);
+                       if (dst) {
+                               ret = base64_decode((const unsigned char *)text,
+                                                   (int)text_len,
+                                                   dst,
+                                                   &dst_len);
+                               if (ret != -1) {
+                                       mg_free(dst);
+                                       return luaL_error(
+                                           L, "illegal character in lsp_base64_decode() call");
+                               } else {
+                                       lua_pushlstring(L, dst, dst_len);
+                                       mg_free(dst);
+                               }
+                       } else {
+                               return luaL_error(L,
+                                                 "out of memory in lsp_base64_decode() call");
+                       }
+               } else {
+                       lua_pushnil(L);
+               }
+       } else {
+               /* Syntax error */
+               return luaL_error(L, "invalid lsp_base64_decode() call");
+       }
+       return 1;
+}
+
+
+/* mg.get_response_code_text */
+static int
+lsp_get_response_code_text(lua_State *L)
+{
+       int num_args = lua_gettop(L);
+       int type1;
+       double code;
+       const char *text;
+
+       if (num_args == 1) {
+               type1 = lua_type(L, 1);
+               if (type1 == LUA_TNUMBER) {
+                       /* If the first argument is a number,
+                          convert it to the corresponding text. */
+                       code = lua_tonumber(L, 1);
+                       text = mg_get_response_code_text(NULL, (int)code);
+                       if (text) { /* <-- should be always true */
+                               lua_pushstring(L, text);
+                       }
+                       return text ? 1 : 0;
+               }
+       }
+
+       /* Syntax error */
+       return luaL_error(L, "invalid get_response_code_text() call");
+}
+
+
+/* mg.random - might be better than math.random on some systems */
+static int
+lsp_random(lua_State *L)
+{
+       int num_args = lua_gettop(L);
+       if (num_args == 0) {
+               /* The civetweb internal random number generator will generate
+                * a 64 bit random number. */
+               uint64_t r = get_random();
+               /* Lua "number" is a IEEE 754 double precission float:
+                * https://en.wikipedia.org/wiki/Double-precision_floating-point_format
+                * Thus, mask with 2^53-1 to get an integer with the maximum
+                * precission available. */
+               r &= ((((uint64_t)1) << 53) - 1);
+               lua_pushnumber(L, (double)r);
+               return 1;
+       }
+
+       /* Syntax error */
+       return luaL_error(L, "invalid random() call");
+}
+
+
+/* mg.get_info */
+static int
+lsp_get_info(lua_State *L)
+{
+       int num_args = lua_gettop(L);
+       int type1, type2;
+       const char *arg1;
+       double arg2;
+       int len;
+       char *buf;
+
+       if (num_args == 1) {
+               type1 = lua_type(L, 1);
+               if (type1 == LUA_TSTRING) {
+                       arg1 = lua_tostring(L, 1);
+                       /* Get info according to argument */
+                       if (!mg_strcasecmp(arg1, "system")) {
+                               /* Get system info */
+                               len = mg_get_system_info(NULL, 0);
+                               if (len > 0) {
+                                       buf = (char *)mg_malloc(len + 64);
+                                       if (!buf) {
+                                               return luaL_error(L, "OOM in get_info() call");
+                                       }
+                                       len = mg_get_system_info(buf, len + 63);
+                                       lua_pushlstring(L, buf, len);
+                                       mg_free(buf);
+                               } else {
+                                       lua_pushstring(L, "");
+                               }
+                               return 1;
+                       }
+                       if (!mg_strcasecmp(arg1, "context")) {
+                               /* Get context */
+                               struct mg_context *ctx;
+                               lua_pushlightuserdata(L, (void *)&lua_regkey_ctx);
+                               lua_gettable(L, LUA_REGISTRYINDEX);
+                               ctx = (struct mg_context *)lua_touserdata(L, -1);
+
+                               /* Get context info for server context */
+                               len = mg_get_context_info(ctx, NULL, 0);
+                               if (len > 0) {
+                                       buf = (char *)mg_malloc(len + 64);
+                                       if (!buf) {
+                                               return luaL_error(L, "OOM in get_info() call");
+                                       }
+                                       len = mg_get_context_info(ctx, buf, len + 63);
+                                       lua_pushlstring(L, buf, len);
+                                       mg_free(buf);
+                               } else {
+                                       lua_pushstring(L, "");
+                               }
+                               return 1;
+                       }
+                       if (!mg_strcasecmp(arg1, "common")) {
+                               /* Get context info for NULL context */
+                               len = mg_get_context_info(NULL, NULL, 0);
+                               if (len > 0) {
+                                       buf = (char *)mg_malloc(len + 64);
+                                       if (!buf) {
+                                               return luaL_error(L, "OOM in get_info() call");
+                                       }
+                                       len = mg_get_context_info(NULL, buf, len + 63);
+                                       lua_pushlstring(L, buf, len);
+                                       mg_free(buf);
+                               } else {
+                                       lua_pushstring(L, "");
+                               }
+                               return 1;
+                       }
+                       return 0;
+               }
+       }
+
+       if (num_args == 2) {
+               type1 = lua_type(L, 1);
+               type2 = lua_type(L, 2);
+               if ((type1 == LUA_TSTRING) && (type2 == LUA_TNUMBER)) {
+                       arg1 = lua_tostring(L, 1);
+                       arg2 = lua_tonumber(L, 2);
+
+                       /* Get info according to argument */
+                       if (!mg_strcasecmp(arg1, "connection")) {
+                               int idx;
+
+                               /* Get context */
+                               struct mg_context *ctx;
+                               lua_pushlightuserdata(L, (void *)&lua_regkey_ctx);
+                               lua_gettable(L, LUA_REGISTRYINDEX);
+                               ctx = (struct mg_context *)lua_touserdata(L, -1);
+
+                               /* Get connection info for connection idx */
+                               idx = (int)(arg2 + 0.5);
+
+                               /* Lua uses 1 based index, C uses 0 based index */
+                               idx--;
+
+#if defined(MG_EXPERIMENTAL_INTERFACES)
+                               len = mg_get_connection_info(ctx, idx, NULL, 0);
+                               if (len > 0) {
+                                       buf = (char *)mg_malloc(len + 64);
+                                       if (!buf) {
+                                               return luaL_error(L, "OOM in get_info() call");
+                                       }
+                                       len = mg_get_connection_info(ctx, idx, buf, len + 63);
+                                       lua_pushlstring(L, buf, len);
+                                       mg_free(buf);
+                               } else {
+                                       lua_pushstring(L, "");
+                               }
+#else
+                               (void)ctx;
+                               (void)idx;
+                               lua_pushstring(L, "");
+#endif
+
+                               return 1;
+                       }
+                       return 0;
+               }
+       }
+
+       /* Syntax error */
+       return luaL_error(L, "invalid get_info() call");
+}
+
+
+/* mg.get_option */
+static int
+lsp_get_option(lua_State *L)
+{
+       int num_args = lua_gettop(L);
+       int type1;
+       const char *arg1;
+       const char *data;
+       int optidx;
+
+       /* Get connection */
+       struct mg_connection *conn =
+           (struct mg_connection *)lua_touserdata(L, lua_upvalueindex(1));
+
+       if (num_args == 0) {
+               const struct mg_option *opts = mg_get_valid_options();
+
+               if (!opts) { /* <-- should be always false */
+                       return 0;
+               }
+
+               lua_newtable(L);
+               while (opts->name) {
+                       optidx = get_option_index(opts->name);
+                       if (optidx >= 0) {
+                               data = conn->dom_ctx->config[optidx];
+                               if (data) {
+                                       reg_string(L, opts->name, data);
+                               }
+                       }
+                       opts++;
+               }
+
+               return 1;
+       }
+
+       if (num_args == 1) {
+               type1 = lua_type(L, 1);
+               if (type1 == LUA_TSTRING) {
+                       arg1 = lua_tostring(L, 1);
+                       /* Get option according to argument */
+                       optidx = get_option_index(arg1);
+                       if (optidx >= 0) {
+                               data = conn->dom_ctx->config[optidx];
+                               if (data) {
+                                       lua_pushstring(L, data);
+                                       return 1;
+                               }
+                       }
+                       return 0;
+               }
+       }
+
+       /* Syntax error */
+       return luaL_error(L, "invalid get_option() call");
+}
+
+
+static int s_lua_traceLevel = 1;
+static FILE *s_lua_traceFile = NULL;
+static pthread_mutex_t s_lua_traceMutex;
+
+
+/* mg.trace */
+static int
+lsp_trace(lua_State *L)
+{
+       int num_args = lua_gettop(L);
+       int arg_type[8];
+       int trace_level = 0;
+       int firstarg = 1;
+       int i;
+
+       for (i = 0; i < 8; i++) {
+               if (num_args >= (i + 1)) {
+                       arg_type[i] = lua_type(L, (i + 1));
+               } else {
+                       arg_type[i] = LUA_TNIL;
+               }
+       }
+
+       if (arg_type[0] == LUA_TNUMBER) {
+               trace_level = (int)lua_tointeger(L, 1);
+               if (num_args == 1) {
+                       /* Set a new trace level, return the current one. */
+                       lua_pushinteger(L, s_lua_traceLevel);
+                       s_lua_traceLevel = trace_level;
+                       if (s_lua_traceFile) {
+                               pthread_mutex_lock(&s_lua_traceMutex);
+                               fflush(s_lua_traceFile);
+                               pthread_mutex_unlock(&s_lua_traceMutex);
+                       }
+                       return 1;
+               }
+               firstarg = 2;
+       }
+
+       if (trace_level > s_lua_traceLevel) {
+               /* If this trace request has a higher trace level than the global trace
+                * level, do not trace. */
+               lua_pushboolean(L, 0);
+               return 1;
+       }
+
+       /* Print to file */
+       if (s_lua_traceFile) {
+               pthread_mutex_lock(&s_lua_traceMutex);
+               for (i = firstarg; i <= num_args; i++) {
+                       if (arg_type[i - 1] == LUA_TSTRING) {
+                               const char *arg = lua_tostring(L, i);
+                               fprintf(s_lua_traceFile, "%s\n", arg);
+                       }
+               }
+               pthread_mutex_unlock(&s_lua_traceMutex);
+       }
+       lua_pushboolean(L, 1);
+       return 1;
+}
+
+
+/* UUID library and function pointer */
+union {
+       void *p;
+       void (*f)(unsigned char uuid[16]);
+} pf_uuid_generate;
+
+
+/* mg.uuid */
+static int
+lsp_uuid(lua_State *L)
+{
+       union {
+               unsigned char uuid_array[16];
+               struct uuid_struct_type {
+                       uint32_t data1;
+                       uint16_t data2;
+                       uint16_t data3;
+                       uint8_t data4[8];
+               } uuid_struct;
+       } uuid;
+
+       char uuid_str[40];
+       int num_args = lua_gettop(L);
+
+       memset(&uuid, 0, sizeof(uuid));
+       memset(uuid_str, 0, sizeof(uuid_str));
+
+       if (num_args == 0) {
+
+               pf_uuid_generate.f(uuid.uuid_array);
+
+               sprintf(uuid_str,
+                       "{%08lX-%04X-%04X-%02X%02X-"
+                       "%02X%02X%02X%02X%02X%02X}",
+                       (unsigned long)uuid.uuid_struct.data1,
+                       (unsigned)uuid.uuid_struct.data2,
+                       (unsigned)uuid.uuid_struct.data3,
+                       (unsigned)uuid.uuid_struct.data4[0],
+                       (unsigned)uuid.uuid_struct.data4[1],
+                       (unsigned)uuid.uuid_struct.data4[2],
+                       (unsigned)uuid.uuid_struct.data4[3],
+                       (unsigned)uuid.uuid_struct.data4[4],
+                       (unsigned)uuid.uuid_struct.data4[5],
+                       (unsigned)uuid.uuid_struct.data4[6],
+                       (unsigned)uuid.uuid_struct.data4[7]);
+
+               lua_pushstring(L, uuid_str);
+               return 1;
+       }
+
+       /* Syntax error */
+       return luaL_error(L, "invalid uuid() call");
+}
+
+
+#if defined(USE_WEBSOCKET)
+struct lua_websock_data {
+       lua_State *state;
+       char *script;
+       unsigned references;
+       struct mg_connection *conn[MAX_WORKER_THREADS];
+       pthread_mutex_t ws_mutex;
+};
+#endif
+
+
+/* mg.write for websockets */
+static int
+lwebsock_write(lua_State *L)
+{
+#if defined(USE_WEBSOCKET)
+       int num_args = lua_gettop(L);
+       struct lua_websock_data *ws;
+       const char *str;
+       size_t size;
+       int opcode = -1;
+       unsigned i;
+       struct mg_connection *client = NULL;
+
+       lua_pushlightuserdata(L, (void *)&lua_regkey_connlist);
+       lua_gettable(L, LUA_REGISTRYINDEX);
+       ws = (struct lua_websock_data *)lua_touserdata(L, -1);
+
+       (void)pthread_mutex_lock(&(ws->ws_mutex));
+
+       if (num_args == 1) {
+               /* just one text: send it to all client */
+               if (lua_isstring(L, 1)) {
+                       opcode = MG_WEBSOCKET_OPCODE_TEXT;
+               }
+       } else if (num_args == 2) {
+               if (lua_isnumber(L, 1)) {
+                       /* opcode number and message text */
+                       opcode = (int)lua_tointeger(L, 1);
+               } else if (lua_isstring(L, 1)) {
+                       /* opcode string and message text */
+                       str = lua_tostring(L, 1);
+                       if (!mg_strncasecmp(str, "text", 4))
+                               opcode = MG_WEBSOCKET_OPCODE_TEXT;
+                       else if (!mg_strncasecmp(str, "bin", 3))
+                               opcode = MG_WEBSOCKET_OPCODE_BINARY;
+                       else if (!mg_strncasecmp(str, "close", 5))
+                               opcode = MG_WEBSOCKET_OPCODE_CONNECTION_CLOSE;
+                       else if (!mg_strncasecmp(str, "ping", 4))
+                               opcode = MG_WEBSOCKET_OPCODE_PING;
+                       else if (!mg_strncasecmp(str, "pong", 4))
+                               opcode = MG_WEBSOCKET_OPCODE_PONG;
+                       else if (!mg_strncasecmp(str, "cont", 4))
+                               opcode = MG_WEBSOCKET_OPCODE_CONTINUATION;
+               } else if (lua_isuserdata(L, 1)) {
+                       /* client id and message text */
+                       client = (struct mg_connection *)lua_touserdata(L, 1);
+                       opcode = MG_WEBSOCKET_OPCODE_TEXT;
+               }
+       } else if (num_args == 3) {
+               if (lua_isuserdata(L, 1)) {
+                       client = (struct mg_connection *)lua_touserdata(L, 1);
+                       if (lua_isnumber(L, 2)) {
+                               /* client id, opcode number and message text */
+                               opcode = (int)lua_tointeger(L, 2);
+                       } else if (lua_isstring(L, 2)) {
+                               /* client id, opcode string and message text */
+                               str = lua_tostring(L, 2);
+                               if (!mg_strncasecmp(str, "text", 4))
+                                       opcode = MG_WEBSOCKET_OPCODE_TEXT;
+                               else if (!mg_strncasecmp(str, "bin", 3))
+                                       opcode = MG_WEBSOCKET_OPCODE_BINARY;
+                               else if (!mg_strncasecmp(str, "close", 5))
+                                       opcode = MG_WEBSOCKET_OPCODE_CONNECTION_CLOSE;
+                               else if (!mg_strncasecmp(str, "ping", 4))
+                                       opcode = MG_WEBSOCKET_OPCODE_PING;
+                               else if (!mg_strncasecmp(str, "pong", 4))
+                                       opcode = MG_WEBSOCKET_OPCODE_PONG;
+                               else if (!mg_strncasecmp(str, "cont", 4))
+                                       opcode = MG_WEBSOCKET_OPCODE_CONTINUATION;
+                       }
+               }
+       }
+
+       if (opcode >= 0 && opcode < 16 && lua_isstring(L, num_args)) {
+               str = lua_tolstring(L, num_args, &size);
+               if (client) {
+                       for (i = 0; i < ws->references; i++) {
+                               if (client == ws->conn[i]) {
+                                       mg_lock_connection(ws->conn[i]);
+                                       mg_websocket_write(ws->conn[i], opcode, str, size);
+                                       mg_unlock_connection(ws->conn[i]);
+                               }
+                       }
+               } else {
+                       for (i = 0; i < ws->references; i++) {
+                               mg_lock_connection(ws->conn[i]);
+                               mg_websocket_write(ws->conn[i], opcode, str, size);
+                               mg_unlock_connection(ws->conn[i]);
+                       }
+               }
+       } else {
+               (void)pthread_mutex_unlock(&(ws->ws_mutex));
+               return luaL_error(L, "invalid websocket write() call");
+       }
+
+       (void)pthread_mutex_unlock(&(ws->ws_mutex));
+
+#else
+       (void)(L);           /* unused */
+#endif
+       return 0;
+}
+
+
+struct laction_string_arg {
+       lua_State *L;
+       const char *script;
+       pthread_mutex_t *pmutex;
+       char txt[1];
+};
+
+struct laction_funcref_arg {
+       lua_State *L;
+       const char *script;
+       pthread_mutex_t *pmutex;
+       int funcref;
+};
+
+
+static int
+lua_action_string(struct laction_string_arg *arg)
+{
+       int err, ok;
+       struct mg_context *ctx;
+
+       (void)pthread_mutex_lock(arg->pmutex);
+
+       lua_pushlightuserdata(arg->L, (void *)&lua_regkey_ctx);
+       lua_gettable(arg->L, LUA_REGISTRYINDEX);
+       ctx = (struct mg_context *)lua_touserdata(arg->L, -1);
+       lua_pop(arg->L, 1);
+
+       err = luaL_loadstring(arg->L, arg->txt);
+       if (err != 0) {
+               struct mg_connection fc;
+               lua_cry(fake_connection(&fc, ctx), err, arg->L, arg->script, "timer");
+               (void)pthread_mutex_unlock(arg->pmutex);
+               return 0;
+       }
+       err = lua_pcall(arg->L, 0, 1, 0);
+       if (err != 0) {
+               struct mg_connection fc;
+               lua_cry(fake_connection(&fc, ctx), err, arg->L, arg->script, "timer");
+               (void)pthread_mutex_unlock(arg->pmutex);
+               return 0;
+       }
+
+       ok = lua_type(arg->L, -1);
+       if (lua_isboolean(arg->L, -1)) {
+               ok = lua_toboolean(arg->L, -1);
+       } else {
+               ok = 0;
+       }
+       lua_pop(arg->L, 1);
+
+       (void)pthread_mutex_unlock(arg->pmutex);
+
+       return ok;
+}
+
+
+static int
+lua_action_funcref(struct laction_funcref_arg *arg)
+{
+       int err, ok;
+       struct mg_context *ctx;
+
+       (void)pthread_mutex_lock(arg->pmutex);
+
+       lua_pushlightuserdata(arg->L, (void *)&lua_regkey_ctx);
+       lua_gettable(arg->L, LUA_REGISTRYINDEX);
+       ctx = (struct mg_context *)lua_touserdata(arg->L, -1);
+       lua_pop(arg->L, 1);
+
+       lua_rawgeti(arg->L, LUA_REGISTRYINDEX, arg->funcref);
+       err = lua_pcall(arg->L, 0, 1, 0);
+       if (err != 0) {
+               struct mg_connection fc;
+               lua_cry(fake_connection(&fc, ctx), err, arg->L, arg->script, "timer");
+               (void)pthread_mutex_unlock(arg->pmutex);
+               return 0;
+       }
+
+       ok = lua_type(arg->L, -1);
+       if (lua_isboolean(arg->L, -1)) {
+               ok = lua_toboolean(arg->L, -1);
+       } else {
+               ok = 0;
+       }
+       lua_pop(arg->L, 1);
+
+       (void)pthread_mutex_unlock(arg->pmutex);
+
+       return ok;
+}
+
+
+static void
+lua_action_string_cancel(struct laction_string_arg *arg)
+{
+       mg_free(arg);
+}
+
+
+static void
+lua_action_funcref_cancel(struct laction_funcref_arg *arg)
+{
+       luaL_unref(arg->L, LUA_REGISTRYINDEX, arg->funcref);
+       mg_free(arg);
+}
+
+
+static int
+lwebsocket_set_timer(lua_State *L, int is_periodic)
+{
+#if defined(USE_TIMERS) && defined(USE_WEBSOCKET)
+       int num_args = lua_gettop(L);
+       struct lua_websock_data *ws;
+       int type1, type2, type3, ok = 0;
+       double delay, interval;
+       struct mg_context *ctx;
+
+       lua_pushlightuserdata(L, (void *)&lua_regkey_ctx);
+       lua_gettable(L, LUA_REGISTRYINDEX);
+       ctx = (struct mg_context *)lua_touserdata(L, -1);
+
+       lua_pushlightuserdata(L, (void *)&lua_regkey_connlist);
+       lua_gettable(L, LUA_REGISTRYINDEX);
+       ws = (struct lua_websock_data *)lua_touserdata(L, -1);
+
+       if (num_args < 2) {
+               return luaL_error(L,
+                                 "not enough arguments for set_timer/interval() call");
+       }
+
+       type1 = lua_type(L, 1);
+       type2 = lua_type(L, 2);
+       type3 = lua_type(L, 3);
+
+       /* Must have at least two arguments, ant the first one has to be some text
+        */
+       if ((num_args < 2) || (num_args > 3)) {
+               return luaL_error(L, "invalid arguments for set_timer/interval() call");
+       }
+
+       /* Second argument is the delay (and interval) */
+       if (type2 != LUA_TNUMBER) {
+               return luaL_error(L, "invalid arguments for set_timer/interval() call");
+       }
+       delay = (double)lua_tonumber(L, 2);
+       interval = (is_periodic ? delay : 0.0);
+
+       /* Third argument (optional) could be an interval */
+       if (num_args > 2) {
+               if (is_periodic || (type3 != LUA_TNUMBER)) {
+                       return luaL_error(
+                           L, "invalid arguments for set_timer/interval() call");
+               }
+               interval = (double)lua_tonumber(L, 3);
+       }
+
+       /* Check numbers */
+       if ((delay < 0.0) || (interval < 0.0)) {
+               return luaL_error(L, "invalid arguments for set_timer/interval() call");
+       }
+
+       /* First value specifies the action */
+       if (type1 == LUA_TSTRING) {
+
+               /* Action could be passed as a string value */
+               struct laction_string_arg *arg;
+               const char *action_txt;
+               size_t action_txt_len;
+
+               action_txt = lua_tostring(L, 1);
+               if ((action_txt == NULL) || (action_txt[0] == 0)) {
+                       return luaL_error(
+                           L, "invalid arguments for set_timer/interval() call");
+               }
+               action_txt_len = strlen(action_txt);
+
+               /* Create timer data structure and schedule timer */
+               arg = (struct laction_string_arg *)mg_malloc_ctx(
+                   sizeof(struct laction_string_arg) + action_txt_len + 10, ctx);
+               if (!arg) {
+                       return luaL_error(L, "out of memory");
+               }
+
+               /* Argument for timer */
+               arg->L = L;
+               arg->script = (ws ? ws->script : NULL);
+               arg->pmutex = (ws ? &(ws->ws_mutex) : &(ctx->lua_bg_mutex));
+               memcpy(arg->txt, "return(", 7);
+               memcpy(arg->txt + 7, action_txt, action_txt_len);
+               arg->txt[action_txt_len + 7] = ')';
+               arg->txt[action_txt_len + 8] = 0;
+               if (0
+                   == timer_add(ctx,
+                                delay,
+                                interval,
+                                1,
+                                (taction)lua_action_string,
+                                (void *)arg,
+                                (tcancelaction)lua_action_string_cancel)) {
+                       /* Timer added successfully */
+                       ok = 1;
+               }
+       } else if (type1 == LUA_TFUNCTION) {
+
+               /* Action could be passed as a function */
+               int funcref;
+               struct laction_funcref_arg *arg;
+
+               lua_pushvalue(L, 1);
+               funcref = luaL_ref(L, LUA_REGISTRYINDEX);
+
+               /* Create timer data structure and schedule timer */
+               arg = (struct laction_funcref_arg *)
+                   mg_malloc_ctx(sizeof(struct laction_funcref_arg), ctx);
+               if (!arg) {
+                       return luaL_error(L, "out of memory");
+               }
+
+               /* Argument for timer */
+               arg->L = L;
+               arg->script = (ws ? ws->script : NULL);
+               arg->pmutex = (ws ? &(ws->ws_mutex) : &(ctx->lua_bg_mutex));
+               arg->funcref = funcref;
+               if (0
+                   == timer_add(ctx,
+                                delay,
+                                interval,
+                                1,
+                                (taction)lua_action_funcref,
+                                (void *)arg,
+                                (tcancelaction)lua_action_funcref_cancel)) {
+                       /* Timer added successfully */
+                       ok = 1;
+               }
+       } else {
+               return luaL_error(L, "invalid arguments for set_timer/interval() call");
+       }
+
+       lua_pushboolean(L, ok);
+       return 1;
+
+#else
+       (void)(L);           /* unused */
+       (void)(is_periodic); /* unused */
+       return 0;
+#endif
+}
+
+
+/* mg.set_timeout for websockets */
+static int
+lwebsocket_set_timeout(lua_State *L)
+{
+       return lwebsocket_set_timer(L, 0);
+}
+
+
+/* mg.set_interval for websockets */
+static int
+lwebsocket_set_interval(lua_State *L)
+{
+       return lwebsocket_set_timer(L, 1);
+}
+
+
+/* mg.response.send() */
+static int
+lsp_response_send(lua_State *L)
+{
+       int http_status;
+       int ret1, ret2, ret3;
+
+       struct mg_connection *conn =
+           (struct mg_connection *)lua_touserdata(L, lua_upvalueindex(1));
+
+       /* Get mg.response - table */
+       lua_getglobal(L, "mg");
+       if (!lua_istable(L, -1)) {
+               return luaL_error(L, "table mg not available");
+       }
+       lua_pushstring(L, "response");
+       lua_rawget(L, -2); /* rawget: no metatable */
+       if (!lua_istable(L, -1)) {
+               return luaL_error(L, "table mg.response not available");
+       }
+
+       /* Get element: status code  */
+       lua_pushstring(L, "status");
+       lua_gettable(L, -2); /* get .. could use metatable */
+       if (!lua_isnumber(L, -1)) {
+               return luaL_error(L, "number mg.response.status not available");
+       }
+       http_status = (int)lua_tonumber(L, -1);
+       lua_pop(L, 1); /* remove number "status" */
+
+       /* Get element: table of http response headers */
+       lua_pushstring(L, "http_headers");
+       lua_gettable(L, -2); /* get .. could use metatable */
+       if (!lua_istable(L, -1)) {
+               return luaL_error(L, "table mg.response.http_headers not available");
+       }
+
+       /* Parameter checks passed, initiate response */
+       ret1 = mg_response_header_start(conn, http_status);
+       if (ret1 != 0) {
+               lua_pushboolean(L, 0); /* false */
+               lua_pushinteger(L, ret1);
+               return 2;
+       }
+
+       /* Iterate table of http response headers */
+       ret2 = 0;
+       lua_pushnil(L);
+       while (lua_next(L, -2)) {
+               int retadd = 0;
+               int key_type = lua_type(L, -2);
+               int value_type = lua_type(L, -1);
+               if ((key_type == LUA_TSTRING) && (value_type == LUA_TSTRING)) {
+                       size_t key_len = 0, value_len = 0;
+                       const char *key = lua_tolstring(L, -2, &key_len);
+                       const char *value = lua_tolstring(L, -1, &value_len);
+                       retadd = mg_response_header_add(conn, key, value, (int)value_len);
+               } else if ((key_type == LUA_TNUMBER) && (value_type == LUA_TSTRING)) {
+                       const char *value = lua_tostring(L, -1);
+                       retadd = mg_response_header_add_lines(conn, value);
+               }
+               if ((retadd != 0) && (ret2 == 0)) {
+                       /* Store first error */
+                       ret2 = retadd;
+               }
+               lua_pop(L, 1);
+       }
+
+       /* Finalize */
+       ret3 = mg_response_header_send(conn);
+       if (ret3 == 0) {
+               lua_pushboolean(L, 1);    /* TRUE */
+               lua_pushinteger(L, ret2); /* Error/Warning from header_add */
+       } else {
+               lua_pushboolean(L, 0);    /* FALSE */
+               lua_pushinteger(L, ret3); /* Error from header_send */
+       }
+       return 2;
+}
+
+
+/* Debug hook */
+static void
+lua_debug_hook(lua_State *L, lua_Debug *ar)
+{
+       int i;
+       int stack_len = lua_gettop(L);
+
+       lua_getinfo(L, "nSlu", ar);
+
+       if (ar->event == LUA_HOOKCALL) {
+               printf("call\n");
+       } else if (ar->event == LUA_HOOKRET) {
+               printf("ret\n");
+#if defined(LUA_HOOKTAILRET)
+       } else if (ar->event == LUA_HOOKTAILRET) {
+               printf("tail ret\n");
+#endif
+#if defined(LUA_HOOKTAILCALL)
+       } else if (ar->event == LUA_HOOKTAILCALL) {
+               printf("tail call\n");
+#endif
+       } else if (ar->event == LUA_HOOKLINE) {
+               printf("line\n");
+       } else if (ar->event == LUA_HOOKCOUNT) {
+               printf("count\n");
+       } else {
+               printf("unknown (%i)\n", ar->event);
+       }
+
+       if (ar->currentline >= 0) {
+               printf("%s:%i\n", ar->source, ar->currentline);
+       }
+
+       printf("%s (%s)\n", ar->name, ar->namewhat);
+
+
+       for (i = 1; i <= stack_len; i++) { /* repeat for each level */
+               int val_type = lua_type(L, i);
+               const char *s;
+               size_t n;
+
+               switch (val_type) {
+
+               case LUA_TNIL:
+                       /* nil value  on the stack */
+                       printf("nil\n");
+                       break;
+
+               case LUA_TBOOLEAN:
+                       /* boolean (true / false) */
+                       printf("boolean: %s\n", lua_toboolean(L, i) ? "true" : "false");
+                       break;
+
+               case LUA_TNUMBER:
+                       /* number */
+                       printf("number: %g\n", lua_tonumber(L, i));
+                       break;
+
+               case LUA_TSTRING:
+                       /* string with limited length */
+                       s = lua_tolstring(L, i, &n);
+                       printf("string: '%.*s%s\n",
+                              ((n > 30) ? 28 : (int)n),
+                              s,
+                              ((n > 30) ? ".." : "'"));
+                       break;
+
+               default:
+                       /* other values */
+                       printf("%s\n", lua_typename(L, val_type));
+                       break;
+               }
+       }
+
+       printf("\n");
+}
+
+
+/* Lua Environment */
+enum {
+       LUA_ENV_TYPE_LUA_SERVER_PAGE = 0, /* page.lp */
+       LUA_ENV_TYPE_PLAIN_LUA_PAGE = 1,  /* script.lua */
+       LUA_ENV_TYPE_LUA_WEBSOCKET = 2,   /* websock.lua */
+       LUA_ENV_TYPE_BACKGROUND = 9 /* Lua backgrond script or exec from cmdline */
+};
+
+
+static void
+push_lua_response_log_data(const struct mg_connection *conn, lua_State *L)
+{
+       int i;
+       const char *s;
+
+       lua_newtable(L);
+
+       /* request status */
+       reg_int(L, "status", conn->status_code);
+
+       /* protocol (http, https, ws, wss) */
+       s = get_proto_name(conn);
+       reg_string(L, "protocol", s);
+
+       /* request counter */
+       reg_int(L, "handled_requests", conn->handled_requests);
+
+       /* data read and written */
+       reg_i64(L, "read", conn->consumed_content);
+       reg_i64(L, "written", conn->num_bytes_sent);
+
+#if !defined(NO_RESPONSE_BUFFERING)
+       lua_pushstring(L, "http_headers");
+       lua_newtable(L);
+       for (i = 0; i < conn->response_info.num_headers; i++) {
+               reg_string(L,
+                          conn->response_info.http_headers[i].name,
+                          conn->response_info.http_headers[i].value);
+       }
+       lua_rawset(L, -3);
+#endif
+
+#if defined(USE_SERVER_STATS)
+       reg_double(L, "processing_time", conn->processing_time);
+#endif
+}
+
+
+static void
+prepare_lua_request_info_inner(const struct mg_connection *conn, lua_State *L)
+{
+       const char *s;
+       int i;
+
+       lua_newtable(L);
+       reg_string(L, "request_method", conn->request_info.request_method);
+       reg_string(L, "request_uri", conn->request_info.request_uri);
+       reg_string(L, "uri", conn->request_info.local_uri);
+       reg_string(L, "uri_raw", conn->request_info.local_uri_raw);
+       reg_string(L, "http_version", conn->request_info.http_version);
+       reg_string(L, "query_string", conn->request_info.query_string);
+       reg_string(L, "remote_addr", conn->request_info.remote_addr);
+       /* TODO (high): ip version */
+       reg_int(L, "remote_port", conn->request_info.remote_port);
+       reg_int(L, "server_port", conn->request_info.server_port);
+       reg_int(L, "num_headers", conn->request_info.num_headers);
+
+       if (conn->path_info != NULL) {
+               reg_string(L, "path_info", conn->path_info);
+       }
+
+       {
+               char buf[2048];
+               if (0 == mg_get_request_link(conn, buf, sizeof(buf))) {
+                       reg_string(L, "request_link", buf);
+               }
+       }
+
+       if (conn->request_info.content_length >= 0) {
+               /* reg_int64: content_length */
+               lua_pushstring(L, "content_length");
+               lua_pushnumber(
+                   L,
+                   (lua_Number)conn->request_info
+                       .content_length); /* lua_Number may be used as 52 bit integer */
+               lua_rawset(L, -3);
+       }
+       if ((s = mg_get_header(conn, "Content-Type")) != NULL) {
+               reg_string(L, "content_type", s);
+       }
+
+       if (conn->request_info.remote_user != NULL) {
+               reg_string(L, "remote_user", conn->request_info.remote_user);
+               reg_string(L, "auth_type", "Digest");
+       }
+
+       reg_boolean(L, "https", conn->ssl != NULL);
+
+       if (conn->status_code > 0) {
+               /* Lua error handler should show the status code */
+               reg_int(L, "status", conn->status_code);
+       }
+
+       /* Table "HTTP headers" */
+       lua_pushstring(L, "http_headers");
+       lua_newtable(L);
+       for (i = 0; i < conn->request_info.num_headers; i++) {
+               reg_string(L,
+                          conn->request_info.http_headers[i].name,
+                          conn->request_info.http_headers[i].value);
+       }
+       lua_rawset(L, -3);
+
+       /* Table "Client Certificate" */
+       if (conn->request_info.client_cert) {
+               lua_pushstring(L, "client_cert");
+               lua_newtable(L);
+               reg_string(L, "subject", conn->request_info.client_cert->subject);
+               reg_string(L, "issuer", conn->request_info.client_cert->issuer);
+               reg_string(L, "serial", conn->request_info.client_cert->serial);
+               reg_string(L, "finger", conn->request_info.client_cert->finger);
+               lua_rawset(L, -3);
+       }
+}
+
+
+static void
+prepare_lua_request_info(const struct mg_connection *conn, lua_State *L)
+{
+       /* Export mg.request_info */
+       lua_pushstring(L, "request_info");
+       prepare_lua_request_info_inner(conn, L);
+
+       /* End of request_info */
+       lua_rawset(L, -3);
+}
+
+
+static void
+prepare_lua_response_table(struct mg_connection *conn, lua_State *L)
+{
+       /* Export mg.request_info */
+       lua_pushstring(L, "response");
+       lua_newtable(L);
+
+       /* Add table elements */
+
+       /* HTTP status code (default to 200 OK) */
+       reg_int(L, "status", 200);
+
+       /* Table "HTTP headers" */
+       lua_pushstring(L, "http_headers");
+       lua_newtable(L);
+       /* Initially empty */
+       lua_rawset(L, -3);
+
+       /* mg_response_header_send wrapper */
+       reg_conn_function(L, "send", lsp_response_send, conn);
+
+       /* End of response table */
+       lua_rawset(L, -3);
+}
+
+
+static void *
+lua_allocator(void *ud, void *ptr, size_t osize, size_t nsize)
+{
+       (void)osize; /* not used */
+
+       if (nsize == 0) {
+               mg_free(ptr);
+               return NULL;
+       }
+
+       return mg_realloc_ctx(ptr, nsize, (struct mg_context *)ud);
+}
+
+
+/* In CivetWeb, Lua-Shared is used as *.inl file */
+#define LUA_SHARED_INTERFACE static
+#include "mod_lua_shared.inl"
+
+
+static void
+civetweb_open_lua_libs(lua_State *L)
+{
+       {
+               extern void luaL_openlibs(lua_State *);
+               luaL_openlibs(L);
+       }
+
+#if defined(USE_LUA_SQLITE3)
+       {
+               extern int luaopen_lsqlite3(lua_State *);
+               luaopen_lsqlite3(L);
+       }
+#endif
+#if defined(USE_LUA_LUAXML)
+       {
+               extern int luaopen_LuaXML_lib(lua_State *);
+               luaopen_LuaXML_lib(L);
+               // lua_pushvalue(L, -1); to copy value
+               lua_setglobal(L, "xml");
+       }
+#endif
+#if defined(USE_LUA_FILE_SYSTEM)
+       {
+               extern int luaopen_lfs(lua_State *);
+               luaopen_lfs(L);
+       }
+#endif
+}
+
+
+/* garbage collect "mg"
+ * this indicates when a Lua state is closed
+ */
+static int
+lsp_mg_gc(lua_State *L)
+{
+       int context_flags;
+       struct mg_context *ctx;
+       struct mg_connection *conn =
+           (struct mg_connection *)lua_touserdata(L, lua_upvalueindex(1));
+
+       lua_pushlightuserdata(L, (void *)&lua_regkey_ctx);
+       lua_gettable(L, LUA_REGISTRYINDEX);
+       ctx = (struct mg_context *)lua_touserdata(L, -1);
+
+       lua_pushlightuserdata(L, (void *)&lua_regkey_environment_type);
+       lua_gettable(L, LUA_REGISTRYINDEX);
+       context_flags = lua_tointeger(L, -1);
+
+       if (ctx != NULL) {
+               if (ctx->callbacks.exit_lua != NULL) {
+                       ctx->callbacks.exit_lua(conn, L, (unsigned)context_flags);
+               }
+       }
+
+       return 0;
+}
+
+
+static void
+reg_gc(lua_State *L, void *conn)
+{
+       /* Key element */
+       lua_pushlightuserdata(L, (void *)&lua_regkey_dtor);
+
+       /* Value element */
+       lua_newuserdata(L, 0);
+
+       /* Prepare metatable for value element */
+       lua_newtable(L);
+
+       /* Add garbage collector key to metatable */
+       lua_pushliteral(L, "__gc");
+
+       /* Add garbage collector function with one upvalue (conn) */
+       lua_pushlightuserdata(L, conn);
+       lua_pushcclosure(L, lsp_mg_gc, 1);
+
+       /* Set __gc = function lsp_mg_gc in metatable */
+       lua_rawset(L, -3);
+
+       /* Set metatable for "value element" */
+       lua_setmetatable(L, -2);
+
+       /* Add key (lightuserdata) = value (userdata with metatable) */
+       lua_settable(L, LUA_REGISTRYINDEX);
+}
+
+
+static int
+lua_error_handler(lua_State *L)
+{
+       const char *error_msg = lua_isstring(L, -1) ? lua_tostring(L, -1) : "?\n";
+
+       lua_getglobal(L, "mg");
+       if (!lua_isnil(L, -1)) {
+               lua_getfield(L, -1, "write"); /* call mg.write() */
+               lua_pushstring(L, error_msg);
+               lua_pushliteral(L, "\n");
+               lua_call(L, 2, 0);
+               IGNORE_UNUSED_RESULT(
+                   luaL_dostring(L, "mg.write(debug.traceback(), '\\n')"));
+       } else {
+               printf("Lua error: [%s]\n", error_msg);
+               IGNORE_UNUSED_RESULT(
+                   luaL_dostring(L, "print(debug.traceback(), '\\n')"));
+       }
+       /* TODO(lsm, low): leave the stack balanced */
+
+       return 0;
+}
+
+
+static void
+prepare_lua_environment(struct mg_context *ctx,
+                        struct mg_connection *conn,
+                        struct lua_websock_data *ws_conn_list,
+                        lua_State *L,
+                        const char *script_name,
+                        int lua_env_type)
+{
+       const char *preload_file_name = NULL;
+       const char *debug_params = NULL;
+
+       int lua_context_flags = lua_env_type;
+
+       civetweb_open_lua_libs(L);
+
+#if defined(MG_EXPERIMENTAL_INTERFACES)
+       /* Check if debugging should be enabled */
+       if ((conn != NULL) && (conn->dom_ctx != NULL)) {
+               debug_params = conn->dom_ctx->config[LUA_DEBUG_PARAMS];
+       }
+#endif
+
+#if LUA_VERSION_NUM == 502
+       /* Keep the "connect" method for compatibility,
+        * but do not backport it to Lua 5.1.
+        * TODO: Redesign the interface.
+        */
+       luaL_newmetatable(L, LUASOCKET);
+       /* self.__index = self */
+       lua_pushvalue(L, -1);
+       lua_setfield(L, -2, "__index");
+       luaL_setfuncs(L, luasocket_methods, 0);
+       lua_pop(L, 1);
+       lua_register(L, "connect", lsp_connect);
+#endif
+
+       /* Store context in the registry */
+       if (ctx != NULL) {
+               lua_pushlightuserdata(L, (void *)&lua_regkey_ctx);
+               lua_pushlightuserdata(L, (void *)ctx);
+               lua_settable(L, LUA_REGISTRYINDEX);
+       }
+       if (ws_conn_list != NULL) {
+               lua_pushlightuserdata(L, (void *)&lua_regkey_connlist);
+               lua_pushlightuserdata(L, (void *)ws_conn_list);
+               lua_settable(L, LUA_REGISTRYINDEX);
+       }
+       lua_pushlightuserdata(L, (void *)&lua_regkey_environment_type);
+       lua_pushinteger(L, lua_context_flags);
+       lua_settable(L, LUA_REGISTRYINDEX);
+
+       /* State close function */
+       reg_gc(L, conn);
+
+       /* Lua server pages store the depth of mg.include, in order
+        * to detect recursions and prevent stack overflows. */
+       if (lua_env_type == LUA_ENV_TYPE_LUA_SERVER_PAGE) {
+               struct lsp_include_history *h;
+               lua_pushlightuserdata(L, (void *)&lua_regkey_lsp_include_history);
+               h = (struct lsp_include_history *)
+                   lua_newuserdata(L, sizeof(struct lsp_include_history));
+               lua_settable(L, LUA_REGISTRYINDEX);
+               memset(h, 0, sizeof(struct lsp_include_history));
+       }
+
+       /* Register mg module */
+       lua_newtable(L);
+
+       switch (lua_env_type) {
+       case LUA_ENV_TYPE_LUA_SERVER_PAGE:
+               reg_string(L, "lua_type", "page");
+               break;
+       case LUA_ENV_TYPE_PLAIN_LUA_PAGE:
+               reg_string(L, "lua_type", "script");
+               break;
+       case LUA_ENV_TYPE_LUA_WEBSOCKET:
+               reg_string(L, "lua_type", "websocket");
+               break;
+       case LUA_ENV_TYPE_BACKGROUND:
+               reg_string(L, "lua_type", "background");
+               break;
+       }
+
+       if ((lua_env_type == LUA_ENV_TYPE_LUA_SERVER_PAGE)
+           || (lua_env_type == LUA_ENV_TYPE_PLAIN_LUA_PAGE)) {
+               reg_conn_function(L, "cry", lsp_cry, conn);
+               reg_conn_function(L, "read", lsp_read, conn);
+               reg_conn_function(L, "write", lsp_write, conn);
+               reg_conn_function(L, "keep_alive", lsp_keep_alive, conn);
+               reg_conn_function(L, "send_file", lsp_send_file, conn);
+               reg_conn_function(L, "send_file_body", lsp_send_file_body, conn);
+               reg_conn_function(L, "send_http_error", lsp_send_http_error, conn);
+               reg_conn_function(L, "send_http_ok", lsp_send_http_ok, conn);
+               reg_conn_function(L,
+                                 "send_http_redirect",
+                                 lsp_send_http_redirect,
+                                 conn);
+               reg_conn_function(L, "redirect", lsp_redirect, conn);
+       }
+
+       if (lua_env_type == LUA_ENV_TYPE_LUA_SERVER_PAGE) {
+               reg_conn_function(L, "include", lsp_include, conn);
+       }
+
+       if (lua_env_type == LUA_ENV_TYPE_LUA_WEBSOCKET) {
+               reg_function(L, "write", lwebsock_write);
+       }
+
+#if defined(USE_TIMERS)
+       if ((lua_env_type == LUA_ENV_TYPE_LUA_WEBSOCKET)
+           || (lua_env_type == LUA_ENV_TYPE_BACKGROUND)) {
+               reg_function(L, "set_timeout", lwebsocket_set_timeout);
+               reg_function(L, "set_interval", lwebsocket_set_interval);
+#endif
+       }
+
+       reg_conn_function(L, "get_mime_type", lsp_get_mime_type, conn);
+       reg_conn_function(L, "get_option", lsp_get_option, conn);
+
+       reg_function(L, "time", lsp_get_time);
+       reg_function(L, "get_var", lsp_get_var);
+       reg_function(L, "split_form_data", lsp_split_form_urlencoded);
+       reg_function(L, "get_cookie", lsp_get_cookie);
+       reg_function(L, "md5", lsp_md5);
+       reg_function(L, "url_encode", lsp_url_encode);
+       reg_function(L, "url_decode", lsp_url_decode);
+       reg_function(L, "base64_encode", lsp_base64_encode);
+       reg_function(L, "base64_decode", lsp_base64_decode);
+       reg_function(L, "get_response_code_text", lsp_get_response_code_text);
+       reg_function(L, "random", lsp_random);
+       reg_function(L, "get_info", lsp_get_info);
+       reg_function(L, "trace", lsp_trace);
+
+       if (pf_uuid_generate.f) {
+               reg_function(L, "uuid", lsp_uuid);
+       }
+
+       reg_string(L, "version", CIVETWEB_VERSION);
+
+       reg_string(L, "script_name", script_name);
+
+       if ((conn != NULL) && (conn->dom_ctx != NULL)) {
+               reg_string(L, "document_root", conn->dom_ctx->config[DOCUMENT_ROOT]);
+               reg_string(L,
+                          "auth_domain",
+                          conn->dom_ctx->config[AUTHENTICATION_DOMAIN]);
+#if defined(USE_WEBSOCKET)
+               if (conn->dom_ctx->config[WEBSOCKET_ROOT]) {
+                       reg_string(L,
+                                  "websocket_root",
+                                  conn->dom_ctx->config[WEBSOCKET_ROOT]);
+               } else {
+                       reg_string(L,
+                                  "websocket_root",
+                                  conn->dom_ctx->config[DOCUMENT_ROOT]);
+               }
+#endif
+
+               if ((ctx != NULL) && (ctx->systemName != NULL)) {
+                       reg_string(L, "system", ctx->systemName);
+               }
+       }
+
+       /* Export connection specific info */
+       if (conn != NULL) {
+               /* mg.request_info.* available for all environments */
+               prepare_lua_request_info(conn, L);
+
+               if (lua_env_type == LUA_ENV_TYPE_PLAIN_LUA_PAGE) {
+                       /* mg.response.* available only for *.lua scripts */
+                       prepare_lua_response_table(conn, L);
+               }
+       }
+
+       /* Store as global table "mg" */
+       lua_setglobal(L, "mg");
+
+       /* Register "shared" table */
+       lua_shared_register(L);
+
+       /* Register default mg.onerror function */
+       IGNORE_UNUSED_RESULT(
+           luaL_dostring(L,
+                         "mg.onerror = function(e) mg.write('\\nLua error:\\n', "
+                         "debug.traceback(e, 1)) end"));
+
+       /* Check if a preload file is available */
+       if ((conn != NULL) && (conn->dom_ctx != NULL)) {
+               preload_file_name = conn->dom_ctx->config[LUA_PRELOAD_FILE];
+       }
+
+       /* Call user init function */
+       if (ctx != NULL) {
+               if (ctx->callbacks.init_lua != NULL) {
+                       ctx->callbacks.init_lua(conn, L, lua_context_flags);
+               }
+       }
+
+       /* Preload file into new Lua environment */
+       if (preload_file_name) {
+               int ret = luaL_loadfile(L, preload_file_name);
+               if (ret != 0) {
+                       lua_error_handler(L);
+               } else {
+                       ret = lua_pcall(L, 0, 1, 0);
+               }
+       }
+
+       /* If debugging is enabled, add a hook */
+       if (debug_params) {
+               int mask = 0;
+               if (0 != strchr(debug_params, 'c')) {
+                       mask |= LUA_MASKCALL;
+               }
+               if (0 != strchr(debug_params, 'r')) {
+                       mask |= LUA_MASKRET;
+               }
+               if (0 != strchr(debug_params, 'l')) {
+                       mask |= LUA_MASKLINE;
+               }
+               lua_sethook(L, lua_debug_hook, mask, 0);
+       }
+}
+
+
+static void
+mg_exec_lua_script(struct mg_connection *conn,
+                   const char *path,
+                   const void **exports)
+{
+       int i;
+       lua_State *L;
+
+       /* Assume the script does not support keep_alive. The script may change this
+        * by calling mg.keep_alive(true). */
+       conn->must_close = 1;
+
+       /* Execute a plain Lua script. */
+       if (path != NULL
+           && (L = lua_newstate(lua_allocator, (void *)(conn->phys_ctx)))
+                  != NULL) {
+               prepare_lua_environment(
+                   conn->phys_ctx, conn, NULL, L, path, LUA_ENV_TYPE_PLAIN_LUA_PAGE);
+               lua_pushcclosure(L, &lua_error_handler, 0);
+
+               if (exports != NULL) {
+#if LUA_VERSION_NUM > 501
+                       lua_pushglobaltable(L);
+                       for (i = 0; exports[i] != NULL && exports[i + 1] != NULL; i += 2) {
+                               lua_CFunction func;
+                               lua_pushstring(L, (const char *)(exports[i]));
+                               *(const void **)(&func) = exports[i + 1];
+                               lua_pushcclosure(L, func, 0);
+                               lua_rawset(L, -3);
+                       }
+#else
+                       for (i = 0; exports[i] != NULL && exports[i + 1] != NULL; i += 2) {
+                               lua_CFunction func;
+                               const char *name = (const char *)(exports[i]);
+                               *(const void **)(&func) = exports[i + 1];
+                               lua_register(L, name, func);
+                       }
+#endif
+               }
+
+               if (luaL_loadfile(L, path) != 0) {
+                       lua_error_handler(L);
+               } else {
+                       lua_pcall(L, 0, 0, -2);
+               }
+               lua_close(L);
+       }
+}
+
+
+static int
+handle_lsp_request(struct mg_connection *conn,
+                   const char *path,
+                   struct mg_file *filep,
+                   struct lua_State *ls)
+{
+       void *p = NULL;
+       lua_State *L = NULL;
+       struct lsp_include_history *include_history;
+       int error = 1;
+       int (*run_lsp)(struct mg_connection *,
+                      const char *,
+                      const char *,
+                      int64_t,
+                      lua_State *,
+                      int);
+       const char *addr;
+
+       /* mg_fopen opens the file and sets the size accordingly */
+       if (!mg_fopen(conn, path, MG_FOPEN_MODE_READ, filep)) {
+
+               /* File not found or not accessible */
+               if (ls == NULL) {
+                       mg_send_http_error(conn,
+                                          500,
+                                          "Error: Cannot open script file %s",
+                                          path);
+               } else {
+                       luaL_error(ls, "Cannot include [%s]: not found", path);
+               }
+
+               goto cleanup_handle_lsp_request;
+       }
+
+       /* Map file in memory (size is known). */
+       if ((p = mmap(NULL,
+                     (size_t)filep->stat.size,
+                     PROT_READ,
+                     MAP_PRIVATE,
+                     fileno(filep->access.fp),
+                     0))
+           == NULL) {
+
+               /* File was not already in memory, and mmap failed now.
+                * Since wi have no data, show an error. */
+               if (ls == NULL) {
+                       /* No open Lua state - use generic error function */
+                       mg_send_http_error(
+                           conn,
+                           500,
+                           "Error: Cannot open script\nFile %s can not be mapped",
+                           path);
+               } else {
+                       /* Lua state exists - use Lua error function */
+                       luaL_error(ls,
+                                  "mmap(%s, %zu, %d): %s",
+                                  path,
+                                  (size_t)filep->stat.size,
+                                  fileno(filep->access.fp),
+                                  strerror(errno));
+               }
+
+               goto cleanup_handle_lsp_request;
+       }
+
+       /* File content is now memory mapped. Get mapping address. */
+       addr = (const char *)p;
+
+       /* Get a Lua state */
+       if (ls != NULL) {
+               /* We got a Lua state as argument. Use it! */
+               L = ls;
+       } else {
+               /* We need to create a Lua state. */
+               L = lua_newstate(lua_allocator, (void *)(conn->phys_ctx));
+               if (L == NULL) {
+                       /* We neither got a Lua state from the command line,
+                        * nor did we succeed in creating our own state.
+                        * Show an error, and stop further processing of this request. */
+                       mg_send_http_error(
+                           conn,
+                           500,
+                           "%s",
+                           "Error: Cannot execute script\nlua_newstate failed");
+
+                       goto cleanup_handle_lsp_request;
+               }
+
+               /* New Lua state needs CivetWeb functions (e.g., the "mg" library). */
+               prepare_lua_environment(
+                   conn->phys_ctx, conn, NULL, L, path, LUA_ENV_TYPE_LUA_SERVER_PAGE);
+       }
+
+       /* Get LSP include history table */
+       lua_pushlightuserdata(L, (void *)&lua_regkey_lsp_include_history);
+       lua_gettable(L, LUA_REGISTRYINDEX);
+       include_history = (struct lsp_include_history *)lua_touserdata(L, -1);
+
+       /* Store script name and increment depth */
+       include_history->depth++;
+       include_history->script[include_history->depth] = path;
+
+       /* Lua state is ready to use now. */
+       /* Currently we have two different syntax options:
+        * Either "classic" CivetWeb syntax:
+        *    <? code ?>
+        *    <?= expression ?>
+        * Or "Kepler Syntax"
+        * https://keplerproject.github.io/cgilua/manual.html#templates
+        *    <?lua chunk ?>
+        *    <?lua= expression ?>
+        *    <% chunk %>
+        *    <%= expression %>
+        *
+        * Two important differences are:
+        * - In the "classic" CivetWeb syntax, the Lua Page had to send the HTTP
+        *   response headers itself. So the first lines are usually something like
+        *   HTTP/1.0 200 OK
+        *   Content-Type: text/html
+        *   followed by additional headers and an empty line, before the actual
+        *   Lua page in HTML syntax with <? code ?> tags.
+        *   The "Kepler"Syntax" does not send any HTTP header from the Lua Server
+        *   Page, but starts directly with <html> code - so it cannot influence
+        *   the HTTP response code, e.g., to send a 301 Moved Permanently.
+        *   Due to this difference, the same *.lp file cannot be used with the
+        *   same algorithm.
+        * - The "Kepler Syntax" used to allow mixtures of Lua and HTML inside an
+        *   incomplete Lua block, e.g.:
+        *   <lua? for i=1,10 do ?><li><%= key %></li><lua? end ?>
+        *   This was not provided in "classic" CivetWeb syntax, but you had to use
+        *   <? for i=1,10 do mg.write("<li>"..i.."</li>") end ?>
+        *   instead. The parsing algorithm for "Kepler syntax" is more complex
+        *   than for "classic" CivetWeb syntax - TODO: check timing/performance.
+        *
+        * CivetWeb now can use both parsing methods, but needs to know what
+        * parsing algorithm should be used.
+        * Idea: Files starting with '<' are HTML files in "Kepler Syntax", except
+        * "<?" which means "classic CivetWeb Syntax".
+        *
+        */
+       run_lsp = run_lsp_civetweb;
+       if ((addr[0] == '<') && (addr[1] != '?')) {
+               run_lsp = run_lsp_kepler;
+       }
+
+       /* We're not sending HTTP headers here, Lua page must do it. */
+       error =
+           run_lsp(conn, path, addr, filep->stat.size, L, include_history->depth);
+
+       /* pop from stack */
+       include_history->depth--;
+
+cleanup_handle_lsp_request:
+
+       if (L != NULL && ls == NULL)
+               lua_close(L);
+       if (p != NULL)
+               munmap(p, filep->stat.size);
+       (void)mg_fclose(&filep->access);
+
+       return error;
+}
+
+
+#if defined(USE_WEBSOCKET)
+struct mg_shared_lua_websocket_list {
+       struct lua_websock_data ws;
+       struct mg_shared_lua_websocket_list *next;
+};
+
+
+static void *
+lua_websocket_new(const char *script, struct mg_connection *conn)
+{
+       struct mg_shared_lua_websocket_list **shared_websock_list =
+           &(conn->dom_ctx->shared_lua_websockets);
+       struct lua_websock_data *ws;
+       int err, ok = 0;
+
+       DEBUG_ASSERT(conn->lua_websocket_state == NULL);
+
+       /* lock list (mg_context global) */
+       mg_lock_context(conn->phys_ctx);
+       while (*shared_websock_list) {
+               /* check if ws already in list */
+               if (0 == strcmp(script, (*shared_websock_list)->ws.script)) {
+                       break;
+               }
+               shared_websock_list = &((*shared_websock_list)->next);
+       }
+
+       if (*shared_websock_list == NULL) {
+               /* add ws to list */
+               *shared_websock_list =
+                   (struct mg_shared_lua_websocket_list *)mg_calloc_ctx(
+                       sizeof(struct mg_shared_lua_websocket_list), 1, conn->phys_ctx);
+               if (*shared_websock_list == NULL) {
+                       conn->must_close = 1;
+                       mg_unlock_context(conn->phys_ctx);
+                       mg_cry_internal(conn,
+                                       "%s",
+                                       "Cannot create shared websocket struct, OOM");
+                       return NULL;
+               }
+               /* init ws list element */
+               ws = &(*shared_websock_list)->ws;
+               ws->script = mg_strdup_ctx(script, conn->phys_ctx);
+               if (!ws->script) {
+                       conn->must_close = 1;
+                       mg_unlock_context(conn->phys_ctx);
+                       mg_cry_internal(conn,
+                                       "%s",
+                                       "Cannot create shared websocket script, OOM");
+                       return NULL;
+               }
+               pthread_mutex_init(&(ws->ws_mutex), &pthread_mutex_attr);
+               (void)pthread_mutex_lock(&(ws->ws_mutex));
+               ws->state = lua_newstate(lua_allocator, (void *)(conn->phys_ctx));
+               ws->conn[0] = conn;
+               ws->references = 1;
+               prepare_lua_environment(conn->phys_ctx,
+                                       conn,
+                                       ws,
+                                       ws->state,
+                                       script,
+                                       LUA_ENV_TYPE_LUA_WEBSOCKET);
+               err = luaL_loadfile(ws->state, script);
+               if (err != 0) {
+                       lua_cry(conn, err, ws->state, script, "load");
+               }
+               err = lua_pcall(ws->state, 0, 0, 0);
+               if (err != 0) {
+                       lua_cry(conn, err, ws->state, script, "init");
+               }
+       } else {
+               /* inc ref count */
+               ws = &(*shared_websock_list)->ws;
+               (void)pthread_mutex_lock(&(ws->ws_mutex));
+               (*shared_websock_list)->ws.conn[(ws->references)++] = conn;
+       }
+       mg_unlock_context(conn->phys_ctx);
+
+       /* call add */
+       lua_getglobal(ws->state, "open");
+       lua_newtable(ws->state);
+       prepare_lua_request_info(conn, ws->state);
+       lua_pushstring(ws->state, "client");
+       lua_pushlightuserdata(ws->state, (void *)conn);
+       lua_rawset(ws->state, -3);
+
+       err = lua_pcall(ws->state, 1, 1, 0);
+       if (err != 0) {
+               lua_cry(conn, err, ws->state, script, "open handler");
+       } else {
+               if (lua_isboolean(ws->state, -1)) {
+                       ok = lua_toboolean(ws->state, -1);
+               }
+               lua_pop(ws->state, 1);
+       }
+       if (!ok) {
+               /* Remove from ws connection list. */
+               /* TODO (mid): Check if list entry and Lua state needs to be deleted
+                * (see websocket_close). */
+               (*shared_websock_list)->ws.conn[--(ws->references)] = 0;
+       }
+
+       (void)pthread_mutex_unlock(&(ws->ws_mutex));
+
+       return ok ? (void *)ws : NULL;
+}
+
+
+static int
+lua_websocket_data(struct mg_connection *conn,
+                   int bits,
+                   char *data,
+                   size_t data_len,
+                   void *ws_arg)
+{
+       struct lua_websock_data *ws = (struct lua_websock_data *)(ws_arg);
+       int err, ok = 0;
+
+       DEBUG_ASSERT(ws != NULL);
+       DEBUG_ASSERT(ws->state != NULL);
+
+       (void)pthread_mutex_lock(&(ws->ws_mutex));
+
+       lua_getglobal(ws->state, "data");
+       lua_newtable(ws->state);
+       lua_pushstring(ws->state, "client");
+       lua_pushlightuserdata(ws->state, (void *)conn);
+       lua_rawset(ws->state, -3);
+       lua_pushstring(ws->state, "bits"); /* TODO: dont use "bits" but fields with
+                                             a meaning according to
+                                             http://tools.ietf.org/html/rfc6455,
+                                             section 5.2 */
+       lua_pushnumber(ws->state, bits);
+       lua_rawset(ws->state, -3);
+       lua_pushstring(ws->state, "data");
+       lua_pushlstring(ws->state, data, data_len);
+       lua_rawset(ws->state, -3);
+
+       err = lua_pcall(ws->state, 1, 1, 0);
+       if (err != 0) {
+               lua_cry(conn, err, ws->state, ws->script, "data handler");
+       } else {
+               if (lua_isboolean(ws->state, -1)) {
+                       ok = lua_toboolean(ws->state, -1);
+               }
+               lua_pop(ws->state, 1);
+       }
+       (void)pthread_mutex_unlock(&(ws->ws_mutex));
+
+       return ok;
+}
+
+
+static int
+lua_websocket_ready(struct mg_connection *conn, void *ws_arg)
+{
+       struct lua_websock_data *ws = (struct lua_websock_data *)(ws_arg);
+       int err, ok = 0;
+
+       DEBUG_ASSERT(ws != NULL);
+       DEBUG_ASSERT(ws->state != NULL);
+
+       (void)pthread_mutex_lock(&(ws->ws_mutex));
+
+       lua_getglobal(ws->state, "ready");
+       lua_newtable(ws->state);
+       lua_pushstring(ws->state, "client");
+       lua_pushlightuserdata(ws->state, (void *)conn);
+       lua_rawset(ws->state, -3);
+       err = lua_pcall(ws->state, 1, 1, 0);
+       if (err != 0) {
+               lua_cry(conn, err, ws->state, ws->script, "ready handler");
+       } else {
+               if (lua_isboolean(ws->state, -1)) {
+                       ok = lua_toboolean(ws->state, -1);
+               }
+               lua_pop(ws->state, 1);
+       }
+
+       (void)pthread_mutex_unlock(&(ws->ws_mutex));
+
+       return ok;
+}
+
+
+static void
+lua_websocket_close(struct mg_connection *conn, void *ws_arg)
+{
+       struct lua_websock_data *ws = (struct lua_websock_data *)(ws_arg);
+       struct mg_shared_lua_websocket_list **shared_websock_list =
+           &(conn->dom_ctx->shared_lua_websockets);
+       int err = 0;
+       unsigned i;
+       int delete_state = 0;
+
+       DEBUG_ASSERT(ws != NULL);
+       DEBUG_ASSERT(ws->state != NULL);
+
+       (void)pthread_mutex_lock(&(ws->ws_mutex));
+
+       lua_getglobal(ws->state, "close");
+       lua_newtable(ws->state);
+       lua_pushstring(ws->state, "client");
+       lua_pushlightuserdata(ws->state, (void *)conn);
+       lua_rawset(ws->state, -3);
+
+       err = lua_pcall(ws->state, 1, 0, 0);
+       if (err != 0) {
+               lua_cry(conn, err, ws->state, ws->script, "close handler");
+       }
+       for (i = 0; i < ws->references; i++) {
+               if (ws->conn[i] == conn) {
+                       ws->references--;
+                       if (ws->references == 0) {
+                               delete_state = 1;
+                               break;
+                       }
+                       ws->conn[i] = ws->conn[ws->references];
+               }
+       }
+       /* TODO: Delete lua_websock_data and remove it from the websocket list.
+          This must only be done, when all connections are closed, and all
+          asynchronous operations and timers are completed/expired. */
+       if (delete_state) {
+               /* lock list (mg_context global) */
+               mg_lock_context(conn->phys_ctx);
+               while (*shared_websock_list) {
+                       /* find ws in list */
+                       if (0 == strcmp(ws->script, (*shared_websock_list)->ws.script)) {
+                               break;
+                       }
+                       shared_websock_list = &((*shared_websock_list)->next);
+               }
+               if (*shared_websock_list != NULL) {
+                       /* TODO: If we close the state here, timers must be stopped first */
+                       /* lua_close(ws->state); <-- other thread will crash, if there are
+                        * still active timers */
+               }
+               mg_unlock_context(conn->phys_ctx);
+       }
+
+       (void)pthread_mutex_unlock(&(ws->ws_mutex));
+}
+#endif
+
+
+static lua_State *
+mg_lua_context_script_prepare(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;
+       }
+
+       /* Add all libraries */
+       prepare_lua_environment(ctx,
+                               NULL /* conn */,
+                               NULL /* WS list*/,
+                               L,
+                               file_name,
+                               LUA_ENV_TYPE_BACKGROUND);
+
+       /* Load lua script file */
+       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);
+               lua_close(L);
+               return 0;
+       }
+       /*      lua_close(L); must be done somewhere else */
+       return L;
+}
+
+
+static lua_State *
+mg_lua_context_script_run(lua_State *L,
+                          const char *file_name,
+                          struct mg_context *ctx,
+                          char *ebuf,
+                          size_t ebuf_len)
+{
+       int lua_ret;
+       const char *lua_err_txt;
+
+       (void)ctx;
+
+       /* The script file is loaded, now call it */
+       lua_ret = lua_pcall(L,
+                           /* no arguments */ 0,
+                           /* zero or one return value */ 1,
+                           /* errors as string 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);
+               lua_close(L);
+               return 0;
+       }
+
+       /* Check optional return value */
+       if (lua_isboolean(L, -1)) {
+               /* A boolean return value false indicates failure */
+               int ret = lua_toboolean(L, -1);
+               if (ret == 0) {
+                       /* Script returned false */
+                       mg_snprintf(NULL,
+                                   NULL, /* No truncation check for ebuf */
+                                   ebuf,
+                                   ebuf_len,
+                                   "Script %s returned false\n",
+                                   file_name);
+                       lua_close(L);
+                       return 0;
+               }
+       }
+
+       /*      lua_close(L); must be done somewhere else */
+       return L;
+}
+
+
+static void
+lua_ctx_init(struct mg_context *ctx)
+{
+#if defined(USE_WEBSOCKET)
+       ctx->dd.shared_lua_websockets = NULL;
+#endif
+}
+
+
+static void
+lua_ctx_exit(struct mg_context *ctx)
+{
+#if defined(USE_WEBSOCKET)
+       struct mg_shared_lua_websocket_list **shared_websock_list =
+           &(ctx->dd.shared_lua_websockets);
+       struct mg_shared_lua_websocket_list *next;
+
+       mg_lock_context(ctx);
+       while (*shared_websock_list) {
+               lua_close((*shared_websock_list)->ws.state);
+               mg_free((*shared_websock_list)->ws.script);
+
+               /* Save "next" pointer before freeing list element */
+               next = ((*shared_websock_list)->next);
+               mg_free(*shared_websock_list);
+               shared_websock_list = &next;
+       }
+       mg_unlock_context(ctx);
+#endif
+}
+
+
+/* Execute Lua script from main */
+int
+run_lua(const char *file_name)
+{
+       int func_ret = EXIT_FAILURE;
+       char ebuf[512] = {0};
+       lua_State *L =
+           mg_lua_context_script_prepare(file_name, NULL, ebuf, sizeof(ebuf));
+
+       if (L) {
+               L = mg_lua_context_script_run(L, 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
+lua_init_optional_libraries(void)
+{
+       /* Create logging mutex */
+       pthread_mutex_init(&s_lua_traceMutex, &pthread_mutex_attr);
+
+       /* shared Lua state */
+       lua_shared_init();
+
+/* UUID library */
+#if !defined(_WIN32)
+       lib_handle_uuid = dlopen("libuuid.so", RTLD_LAZY);
+       pf_uuid_generate.p =
+           (lib_handle_uuid ? dlsym(lib_handle_uuid, "uuid_generate") : 0);
+#else
+       pf_uuid_generate.p = 0;
+#endif
+}
+
+
+static void
+lua_exit_optional_libraries(void)
+{
+/* UUID library */
+#if !defined(_WIN32)
+       if (lib_handle_uuid) {
+               dlclose(lib_handle_uuid);
+       }
+#endif
+       pf_uuid_generate.p = 0;
+       lib_handle_uuid = NULL;
+
+       /* shared Lua state */
+       lua_shared_exit();
+
+       /* Delete logging mutex */
+       pthread_mutex_destroy(&s_lua_traceMutex);
+}
+
+
+/* End of mod_lua.inl */