浏览代码

Merge pull request #47 from bel2125/master

Various extensions for Lua
sunsetbrew 11 年之前
父节点
当前提交
50f7dce707
共有 15 个文件被更改,包括 1027 次插入121 次删除
  1. 7 0
      .gitignore
  2. 30 8
      docs/UserManual.md
  3. 19 4
      src/civetweb.c
  4. 318 58
      src/mod_lua.inl
  5. 149 0
      test/HugeText.lua
  6. 37 0
      test/html_esc.lua
  7. 69 0
      test/page.lua
  8. 18 11
      test/page2.lp
  9. 46 40
      test/page2.lua
  10. 34 0
      test/page3.lua
  11. 128 0
      test/page4.lua
  12. 31 0
      test/page_keep_alive.lua
  13. 65 0
      test/page_keep_alive_chunked.lua
  14. 2 0
      test/require_test.lua
  15. 74 0
      test/resource_script_demo.lua

+ 7 - 0
.gitignore

@@ -223,3 +223,10 @@ pip-log.txt
 
 #Mr Developer
 .mr.developer.cfg
+
+
+##########################
+## Files created by tests
+##########################
+requests.db
+

+ 30 - 8
docs/UserManual.md

@@ -392,14 +392,36 @@ in Lua. An example is given in
 
 Civetweb exports the following functions to Lua:
 
-    mg.read()         -- reads a chunk from POST data, returns it as a string
-    mg.write(str)     -- writes string to the client
-    mg.include(path)  -- sources another Lua file
-    mg.redirect(uri)  -- internal redirect to a given URI
-    mg.onerror(msg)   -- error handler, can be overridden
-    mg.version        -- a string that holds Civetweb version
-    mg.request_info   -- a table with request information
-
+mg (table):
+    mg.read()                  -- reads a chunk from POST data, returns it as a string
+    mg.write(str)              -- writes string to the client
+    mg.include(path)           -- sources another Lua file
+    mg.redirect(uri)           -- internal redirect to a given URI
+    mg.onerror(msg)            -- error handler, can be overridden
+    mg.version                 -- a string that holds Civetweb version
+    mg.document_root           -- a string that holds the document root directory
+    mg.auth_domain             -- a string that holds the HTTP authentication domain
+    mg.get_var(str, varname)   -- extract variable from (query) string
+    mg.get_cookie(str, cookie) -- extract cookie from a string    
+    mg.get_mime_type(filename) -- get MIME type of a file
+    mg.send_file(filename)     -- send a file, including MIME type
+    mg.url_encode(str)         -- URL encode a string
+    mg.url_decode(str)         -- URL decode a string
+    mg.md5(str)                -- return the MD5 hash of a string
+    mg.keep_alive(bool)        -- allow/forbid to use http keep-alive for this request
+    mg.request_info            -- a table with the following request information
+         .remote_addr          -- IP address of the client as string
+         .remote_port          -- remote port number
+         .server_port          -- server port number
+         .request_method       -- HTTP method (e.g.: GET, POST)
+         .http_version         -- HTTP protocol version (e.g.: 1.1)
+         .uri                  -- resource name
+         .query_string         -- query string if present, nil otherwise
+         .script_name          -- name of the Lua script
+         .https                -- true if accessed by https://, false otherwise
+         .remote_user          -- user name if authenticated, nil otherwise 
+
+connect (function):
     -- Connect to the remote TCP server. This function is an implementation
     -- of simple socket interface. It returns a socket object with three
     -- methods: send, recv, close, which are synchronous (blocking).

+ 19 - 4
src/civetweb.c

@@ -531,6 +531,9 @@ enum {
 #if defined(USE_WEBSOCKET)
     WEBSOCKET_ROOT,
 #endif
+#if defined(USE_LUA) && defined(USE_WEBSOCKET)
+    LUA_WEBSOCKET_EXTENSIONS,
+#endif
 
     NUM_OPTIONS
 };
@@ -573,6 +576,9 @@ static const char *config_options[] = {
 #if defined(USE_WEBSOCKET)
     "websocket_root", NULL,
 #endif
+#if defined(USE_LUA) && defined(USE_WEBSOCKET)
+    "lua_websocket_pattern", "**.lua$",
+#endif
 
     NULL
 };
@@ -4795,6 +4801,12 @@ int mg_websocket_write(struct mg_connection* conn, int opcode, const char* data,
 static void handle_websocket_request(struct mg_connection *conn, const char *path, int is_script_resource)
 {
     const char *version = mg_get_header(conn, "Sec-WebSocket-Version");
+#ifdef USE_LUA
+    int lua_websock, shared_lua_websock = 0; 
+    /* TODO: A websocket script may be shared between several clients, allowing them to communicate
+             directly instead of writing to a data base and polling the data base. */
+#endif
+
     if (version == NULL || strcmp(version, "13") != 0) {
         send_http_error(conn, 426, "Upgrade Required", "%s", "Upgrade Required");
     } else if (conn->ctx->callbacks.websocket_connect != NULL &&
@@ -4803,10 +4815,13 @@ static void handle_websocket_request(struct mg_connection *conn, const char *pat
         /* The C callback is called before Lua and may prevent Lua from handling the websocket. */
     } else {
 #ifdef USE_LUA
-        if (match_prefix(conn->ctx->config[LUA_SCRIPT_EXTENSIONS],
-                         (int)strlen(conn->ctx->config[LUA_SCRIPT_EXTENSIONS]),
-                         path)) {
-            conn->lua_websocket_state = new_lua_websocket(path, conn);
+        lua_websock = conn->ctx->config[LUA_WEBSOCKET_EXTENSIONS] ?
+                          match_prefix(conn->ctx->config[LUA_WEBSOCKET_EXTENSIONS],
+                                       (int)strlen(conn->ctx->config[LUA_WEBSOCKET_EXTENSIONS]),
+                                       path) : 0;
+
+        if (lua_websock || shared_lua_websock) {
+            conn->lua_websocket_state = lua_websocket_new(path, conn, !!shared_lua_websock);
             if (conn->lua_websocket_state) {
                 send_websocket_handshake(conn);
                 if (lua_websocket_ready(conn)) {

+ 318 - 58
src/mod_lua.inl

@@ -72,7 +72,8 @@ static void reg_function(struct lua_State *L, const char *name,
 
 static int lsp_sock_close(lua_State *L)
 {
-    if (lua_gettop(L) > 0 && lua_istable(L, -1)) {
+    int num_args = lua_gettop(L);
+    if ((num_args == 1) && lua_istable(L, -1)) {
         lua_getfield(L, -1, "sock");
         closesocket((SOCKET) lua_tonumber(L, -1));
     } else {
@@ -83,10 +84,11 @@ static int lsp_sock_close(lua_State *L)
 
 static int lsp_sock_recv(lua_State *L)
 {
+    int num_args = lua_gettop(L);
     char buf[2000];
     int n;
 
-    if (lua_gettop(L) > 0 && lua_istable(L, -1)) {
+    if ((num_args == 1) && lua_istable(L, -1)) {
         lua_getfield(L, -1, "sock");
         n = recv((SOCKET) lua_tonumber(L, -1), buf, sizeof(buf), 0);
         if (n <= 0) {
@@ -102,11 +104,12 @@ static int lsp_sock_recv(lua_State *L)
 
 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, sock;
 
-    if (lua_gettop(L) > 1 && lua_istable(L, -2) && lua_isstring(L, -1)) {
+    if ((num_args == 2) && lua_istable(L, -2) && lua_isstring(L, -1)) {
         buf = lua_tolstring(L, -1, &len);
         lua_getfield(L, -2, "sock");
         sock = (int) lua_tonumber(L, -1);
@@ -132,10 +135,11 @@ static const struct luaL_Reg luasocket_methods[] = {
 
 static int lsp_connect(lua_State *L)
 {
+    int num_args = lua_gettop(L);
     char ebuf[100];
     SOCKET sock;
 
-    if (lua_isstring(L, -3) && lua_isnumber(L, -2) && lua_isnumber(L, -1)) {
+    if ((num_args == 3) && lua_isstring(L, -3) && lua_isnumber(L, -2) && lua_isnumber(L, -1)) {
         sock = conn2(NULL, lua_tostring(L, -3), (int) lua_tonumber(L, -2),
             (int) lua_tonumber(L, -1), ebuf, sizeof(ebuf));
         if (sock == INVALID_SOCKET) {
@@ -187,21 +191,21 @@ static const char * lsp_var_reader(lua_State *L, void *ud, size_t *sz)
     const char * ret;
 
     switch (reader->state) {
-        case 0:
-            ret = "mg.write(";
-            *sz = strlen(ret);
-            break;
-        case 1:
-            ret = reader->begin;
-            *sz = reader->len;
-            break;
-        case 2:
-            ret = ")";
-            *sz = strlen(ret);
-            break;
-        default:
-            ret = 0;
-            *sz = 0;
+    case 0:
+        ret = "mg.write(";
+        *sz = strlen(ret);
+        break;
+    case 1:
+        ret = reader->begin;
+        *sz = reader->len;
+        break;
+    case 2:
+        ret = ")";
+        *sz = strlen(ret);
+        break;
+    default:
+        ret = 0;
+        *sz = 0;
     }
 
     reader->state++;
@@ -219,6 +223,7 @@ static int lsp(struct mg_connection *conn, const char *path,
         if (p[i] == '\n') lines++;
         if ((i + 1) < len && p[i] == '<' && p[i + 1] == '?') {
 
+            /* <?= ?> means a variable is enclosed and its value should be printed */
             is_var = ((i + 2) < len && p[i + 2] == '=');
 
             if (is_var) j = i + 2;
@@ -271,14 +276,15 @@ static int lsp(struct mg_connection *conn, const char *path,
     return 0;
 }
 
+/* mg.write: Send data to the client */
 static int lsp_write(lua_State *L)
 {
-    int i, num_args;
+    struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1));
+    int num_args = lua_gettop(L);
     const char *str;
     size_t size;
-    struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1));
+    int i;
 
-    num_args = lua_gettop(L);
     for (i = 1; i <= num_args; i++) {
         if (lua_isstring(L, i)) {
             str = lua_tolstring(L, i, &size);
@@ -289,6 +295,7 @@ static int lsp_write(lua_State *L)
     return 0;
 }
 
+/* mg.read: Read data from the client (e.g., from a POST request) */
 static int lsp_read(lua_State *L)
 {
     struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1));
@@ -301,15 +308,43 @@ static int lsp_read(lua_State *L)
     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 = 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;
+}
+
 /* mg.include: Include another .lp file */
 static int lsp_include(lua_State *L)
 {
     struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1));
+    int num_args = lua_gettop(L);
     struct file file = STRUCT_FILE_INITIALIZER;
-    if (handle_lsp_request(conn, lua_tostring(L, -1), &file, L)) {
-        /* handle_lsp_request returned an error code, meaning an error occured in
-        the included page and mg.onerror returned non-zero. Stop processing. */
-        lsp_abort(L);
+    const char * filename = (num_args == 1) ? lua_tostring(L, 1) : NULL;
+
+    if (filename) {
+        if (handle_lsp_request(conn, filename, &file, L)) {
+            /* handle_lsp_request returned an error code, meaning an error occured 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;
 }
@@ -318,7 +353,15 @@ static int lsp_include(lua_State *L)
 static int lsp_cry(lua_State *L)
 {
     struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1));
-    mg_cry(conn, "%s", lua_tostring(L, -1));
+    int num_args = lua_gettop(L);
+    const char * text = (num_args == 1) ? lua_tostring(L, 1) : NULL;
+
+    if (text) {
+        mg_cry(conn, "%s", lua_tostring(L, -1));
+    } else {
+        /* Syntax error */
+        return luaL_error(L, "invalid cry() call");
+    }
     return 0;
 }
 
@@ -326,12 +369,197 @@ static int lsp_cry(lua_State *L)
 static int lsp_redirect(lua_State *L)
 {
     struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1));
-    conn->request_info.uri = lua_tostring(L, -1);
-    handle_request(conn);
-    lsp_abort(L);
+    int num_args = lua_gettop(L);
+    const char * target = (num_args == 1) ? lua_tostring(L, 1) : NULL;
+
+    if (target) {
+        conn->request_info.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 = 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.get_var */
+static int lsp_get_var(lua_State *L)
+{
+    struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1));
+    int num_args = lua_gettop(L);
+    const char *data, *var_name;
+    size_t data_len, occurrence;
+    int ret;
+    char dst[512];
+
+    if (num_args>=2 && num_args<=3) {
+        data = lua_tolstring(L, 1, &data_len);
+        var_name = lua_tostring(L, 2);
+        occurrence = (num_args>2) ? (long)lua_tonumber(L, 3) : 0;
+
+        ret = mg_get_var2(data, data_len, var_name, dst, sizeof(dst), occurrence);
+        if (ret>=0) {
+            /* Variable found: return value to Lua */
+            lua_pushstring(L, dst);
+        } else {
+            /* Variable not found (TODO: may be string too long) */
+            lua_pushnil(L);
+        }
+    } else {
+        /* Syntax error */
+        return luaL_error(L, "invalid get_var() call");
+    }
+    return 1;
+}
+
+/* mg.get_mime_type */
+static int lsp_get_mime_type(lua_State *L)
+{
+    struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1));
+    int num_args = lua_gettop(L);
+    struct vec mime_type = {0};
+    const char *text;
+
+    if (num_args==1) {
+        text = lua_tostring(L, 1);
+        if (text) {
+            get_mime_type(conn->ctx, text, &mime_type);
+            lua_pushlstring(L, mime_type.ptr, mime_type.len);
+        } else {
+            lua_pushnil(L);
+        }
+    } 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);
+    struct vec mime_type = {0};
+    const char *cookie;
+    const char *var_name;
+    int ret;
+    char dst[512];
+
+    if (num_args==2) {
+        cookie = lua_tostring(L, 1);
+        var_name = lua_tostring(L, 2);
+        if (cookie!=NULL && var_name!=NULL) {
+            ret = mg_get_cookie(cookie, var_name, dst, sizeof(dst));
+        } else {
+            ret = -1;
+        }
+
+        if (ret>=0) {
+            lua_pushlstring(L, dst, ret);
+        } else {
+            lua_pushnil(L);
+        }
+    } 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[512];
+
+    if (num_args==1) {
+        text = lua_tolstring(L, 1, &text_len);
+        if (text) {
+            mg_url_encode(text, dst, sizeof(dst));
+            lua_pushstring(L, dst);
+        } 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[512];
+
+    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) {
+            mg_url_decode(text, text_len, dst, sizeof(dst), is_form);
+            lua_pushstring(L, dst);
+        } else {
+            lua_pushnil(L);
+        }
+    } else {
+        /* Syntax error */
+        return luaL_error(L, "invalid url_decode() call");
+    }
+    return 1;
+}
+
+/* mg.write for websockets */
 static int lwebsock_write(lua_State *L)
 {
 #ifdef USE_WEBSOCKET
@@ -404,7 +632,10 @@ static void prepare_lua_environment(struct mg_connection *conn, lua_State *L, co
     lua_pop(L, 1);
     lua_register(L, "connect", lsp_connect);
 
-    if (conn == NULL) return;
+    if (conn == NULL) {
+        /* Do not register any connection specific functions or variables */
+        return;
+    }
 
     /* Register mg module */
     lua_newtable(L);
@@ -426,6 +657,7 @@ static void prepare_lua_environment(struct mg_connection *conn, lua_State *L, co
     if (lua_env_type==LUA_ENV_TYPE_LUA_SERVER_PAGE || lua_env_type==LUA_ENV_TYPE_PLAIN_LUA_PAGE) {
         reg_function(L, "read", lsp_read, conn);
         reg_function(L, "write", lsp_write, conn);
+        reg_function(L, "keep_alive", lsp_keep_alive, conn);
     }
 
     if (lua_env_type==LUA_ENV_TYPE_LUA_SERVER_PAGE) {
@@ -437,6 +669,14 @@ static void prepare_lua_environment(struct mg_connection *conn, lua_State *L, co
         reg_function(L, "write", lwebsock_write, conn);
     }
 
+    reg_function(L, "send_file", lsp_send_file, conn);
+    reg_function(L, "get_var", lsp_get_var, conn);
+    reg_function(L, "get_mime_type", lsp_get_mime_type, conn);
+    reg_function(L, "get_cookie", lsp_get_cookie, conn);
+    reg_function(L, "md5", lsp_md5, conn);
+    reg_function(L, "url_encode", lsp_url_encode, conn);
+    reg_function(L, "url_decode", lsp_url_decode, conn);
+
     reg_string(L, "version", CIVETWEB_VERSION);
     reg_string(L, "document_root", conn->ctx->config[DOCUMENT_ROOT]);
     reg_string(L, "auth_domain", conn->ctx->config[AUTHENTICATION_DOMAIN]);
@@ -507,6 +747,10 @@ void mg_exec_lua_script(struct mg_connection *conn, const char *path,
     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 = luaL_newstate()) != NULL) {
         prepare_lua_environment(conn, L, path, LUA_ENV_TYPE_PLAIN_LUA_PAGE);
         lua_pushcclosure(L, &lua_error_handler, 0);
@@ -526,7 +770,6 @@ void mg_exec_lua_script(struct mg_connection *conn, const char *path,
         lua_pcall(L, 0, 0, -2);
         lua_close(L);
     }
-    conn->must_close=1;
 }
 
 static void lsp_send_err(struct mg_connection *conn, struct lua_State *L,
@@ -555,6 +798,9 @@ struct file *filep, struct lua_State *ls)
     lua_State *L = NULL;
     int error = 1;
 
+    /* Assume the script does not support keep_alive. The script may change this by calling mg.keep_alive(true). */
+    conn->must_close=1;
+
     /* We need both mg_stat to get file size, and mg_fopen to get fd */
     if (!mg_stat(conn, path, filep) || !mg_fopen(conn, path, "r", filep)) {
         lsp_send_err(conn, ls, "File [%s] not found", path);
@@ -580,7 +826,6 @@ struct file *filep, struct lua_State *ls)
     if (L != NULL && ls == NULL) lua_close(L);
     if (p != NULL) munmap(p, filep->size);
     mg_fclose(filep);
-    conn->must_close=1;
     return error;
 }
 
@@ -588,36 +833,37 @@ struct file *filep, struct lua_State *ls)
 struct lua_websock_data {
     lua_State *main;
     lua_State *thread;
+    struct mg_connection *conn;
 };
 
 static void websock_cry(struct mg_connection *conn, int err, lua_State * L, const char * ws_operation, const char * lua_operation)
 {
     switch (err) {
-    case LUA_OK:
-    case LUA_YIELD:
-        break;
-    case LUA_ERRRUN:
-        mg_cry(conn, "%s: %s failed: runtime error: %s", ws_operation, lua_operation, lua_tostring(L, -1));
-        break;
-    case LUA_ERRSYNTAX:
-        mg_cry(conn, "%s: %s failed: syntax error: %s", ws_operation, lua_operation, lua_tostring(L, -1));
-        break;
-    case LUA_ERRMEM:
-        mg_cry(conn, "%s: %s failed: out of memory", ws_operation, lua_operation);
-        break;
-    case LUA_ERRGCMM:
-        mg_cry(conn, "%s: %s failed: error during garbage collection", ws_operation, lua_operation);
-        break;
-    case LUA_ERRERR:
-        mg_cry(conn, "%s: %s failed: error in error handling: %s", ws_operation, lua_operation, lua_tostring(L, -1));
-        break;
-    default:
-        mg_cry(conn, "%s: %s failed: error %i", ws_operation, lua_operation, err);
-        break;
+        case LUA_OK:
+        case LUA_YIELD:
+            break;
+        case LUA_ERRRUN:
+            mg_cry(conn, "%s: %s failed: runtime error: %s", ws_operation, lua_operation, lua_tostring(L, -1));
+            break;
+        case LUA_ERRSYNTAX:
+            mg_cry(conn, "%s: %s failed: syntax error: %s", ws_operation, lua_operation, lua_tostring(L, -1));
+            break;
+        case LUA_ERRMEM:
+            mg_cry(conn, "%s: %s failed: out of memory", ws_operation, lua_operation);
+            break;
+        case LUA_ERRGCMM:
+            mg_cry(conn, "%s: %s failed: error during garbage collection", ws_operation, lua_operation);
+            break;
+        case LUA_ERRERR:
+            mg_cry(conn, "%s: %s failed: error in error handling: %s", ws_operation, lua_operation, lua_tostring(L, -1));
+            break;
+        default:
+            mg_cry(conn, "%s: %s failed: error %i", ws_operation, lua_operation, err);
+            break;
     }
 }
 
-static void * new_lua_websocket(const char * script, struct mg_connection *conn)
+static void * lua_websocket_new(const char * script, struct mg_connection *conn, int is_shared)
 {
     struct lua_websock_data *lws_data;
     int ok = 0;
@@ -627,6 +873,14 @@ static void * new_lua_websocket(const char * script, struct mg_connection *conn)
     lws_data = (struct lua_websock_data *) malloc(sizeof(*lws_data));
 
     if (lws_data) {
+        lws_data->conn = conn;
+
+        if (is_shared) {
+            (void)pthread_mutex_lock(&conn->ctx->mutex);
+            // TODO: add_to_websocket_list(lws_data);
+            (void)pthread_mutex_unlock(&conn->ctx->mutex);
+        }
+
         lws_data->main = luaL_newstate();
         if (lws_data->main) {
             prepare_lua_environment(conn, lws_data->main, script, LUA_ENV_TYPE_LUA_WEBSOCKET);
@@ -657,6 +911,8 @@ static void * new_lua_websocket(const char * script, struct mg_connection *conn)
             free(lws_data);
             lws_data=0;
         }
+    } else {
+        mg_cry(conn, "%s: out of memory", __func__);
     }
 
     return lws_data;
@@ -674,8 +930,11 @@ static int lua_websocket_data(struct mg_connection *conn, int bits, char *data,
 
     do {
         retry=0;
+
+        /* Push the data to Lua, then resume the Lua state. */
+        /* The data will be available to Lua as the result of the coroutine.yield function. */
         lua_pushboolean(lws_data->thread, 1);
-        if (bits > 0) {
+        if (bits >= 0) {
             lua_pushinteger(lws_data->thread, bits);
             if (data) {
                 lua_pushlstring(lws_data->thread, data, data_len);
@@ -687,6 +946,7 @@ static int lua_websocket_data(struct mg_connection *conn, int bits, char *data,
             err = lua_resume(lws_data->thread, NULL, 1);
         }
 
+        /* Check if Lua returned by a call to the coroutine.yield function. */
         if (err!=LUA_YIELD) {
             websock_cry(conn, err, lws_data->thread, __func__, "lua_resume");
         } else {
@@ -697,7 +957,7 @@ static int lua_websocket_data(struct mg_connection *conn, int bits, char *data,
                 fd_set rfds;
                 struct timeval tv;
 
-                FD_ZERO(&rfds);
+                FD_ZERO(&rfds);
                 FD_SET(conn->client.sock, &rfds);
 
                 tv.tv_sec = (unsigned long)delay;
@@ -731,4 +991,4 @@ static void lua_websocket_close(struct mg_connection *conn)
     free(lws_data);
     conn->lua_websocket_state = NULL;
 }
-#endif
+#endif

+ 149 - 0
test/HugeText.lua

@@ -0,0 +1,149 @@
+-- (c) bel2125, 2010
+-- MIT public licence
+
+
+local letterCode = {
+  [' '] = {0,0,0,0,0},
+  ['!'] = {0,0,95,0,0},
+  ['"'] = {0,3,4,3,0},
+  ['#'] = {34,127,34,127,34},
+  ['$'] = {36,42,127,42,18},
+  ['%'] = {35,19,8,100,98},
+  ['&'] = {54,73,85,34,80},
+  ["'"] = {0,11,7,0,0},
+  ['('] = {0,28,34,65,0},
+  [')'] = {0,65,34,28,0},
+  ['*'] = {20,8,62,8,20},
+  ['+'] = {8,8,62,8,8},
+  [','] = {0,88,56,0,0},
+  ['-'] = {8,8,8,8,8},
+  ['.'] = {0,96,96,0,0},
+  ['/'] = {32,16,8,4,2},
+  ['0'] = {62,81,73,69,62},
+  ['1'] = {0,66,127,64,0},
+  ['2'] = {66,97,81,73,70},
+  ['3'] = {65,73,77,75,49},
+  ['4'] = {24,20,18,127,16},
+  ['5'] = {39,69,69,69,57},
+  ['6'] = {60,74,73,73,48},
+  ['7'] = {1,1,121,5,3},
+  ['8'] = {54,73,73,73,54},
+  ['9'] = {6,73,73,41,30},
+  [':'] = {0,54,54,0,0},
+  [';'] = {0,91,59,0,0},
+  ['<'] = {8,20,34,65,0},
+  ['='] = {20,20,20,20,20},
+  ['>'] = {0,65,34,20,8},
+  ['?'] = {2,1,81,9,6},
+  ['@'] = {50,73,121,65,62},
+  ['A'] = {124,18,17,18,124},
+  ['B'] = {65,127,73,73,54},
+  ['C'] = {62,65,65,65,34},
+  ['D'] = {65,127,65,65,62},
+  ['E'] = {127,73,73,73,65},
+  ['F'] = {127,9,9,9,1},
+  ['G'] = {62,65,65,73,57},
+  ['H'] = {127,8,8,8,127},
+  ['I'] = {0,65,127,65,0},
+  ['J'] = {32,64,65,63,1},
+  ['K'] = {127,8,20,34,65},
+  ['L'] = {127,64,64,64,64},
+  ['M'] = {127,2,12,2,127},
+  ['N'] = {127,4,8,16,127},
+  ['O'] = {62,65,65,65,62},
+  ['P'] = {127,9,9,9,6},
+  ['Q'] = {62,65,81,33,94},
+  ['R'] = {127,9,25,41,70},
+  ['S'] = {38,73,73,73,50},
+  ['T'] = {1,1,127,1,1},
+  ['U'] = {63,64,64,64,63},
+  ['V'] = {7,24,96,24,7},
+  ['W'] = {127,32,24,32,127},
+  ['X'] = {99,20,8,20,99},
+  ['Y'] = {3,4,120,4,3},
+  ['Z'] = {97,81,73,69,67},
+  ['['] = {0,127,65,65,0},
+  ['\\'] = {2,4,8,16,32},
+  [']'] = {0,65,65,127,0},
+  ['^'] = {24,4,2,4,24},
+  ['_'] = {64,64,64,64,64},
+  ['`'] = {0,0,7,11,0},
+  ['a'] = {56,68,68,60,64},
+  ['b'] = {127,72,68,68,56},
+  ['c'] = {56,68,68,68,32},
+  ['d'] = {56,68,68,72,127},
+  ['e'] = {56,84,84,84,24},
+  ['f'] = {0,8,126,9,2},
+  ['g'] = {8,84,84,60,0},
+  ['h'] = {127,4,4,120,0},
+  ['i'] = {0,0,125,0,0},
+  ['j'] = {32,64,68,61,0},
+  ['k'] = {127,16,40,68,0},
+  ['l'] = {0,0,127,0,0},
+  ['m'] = {120,4,120,4,120},
+  ['n'] = {124,8,4,4,120},
+  ['o'] = {56,68,68,68,56},
+  ['p'] = {124,20,20,20,8},
+  ['q'] = {24,36,20,124,64},
+  ['r'] = {124,8,4,4,0},
+  ['s'] = {72,84,84,84,32},
+  ['t'] = {4,62,68,32,0},
+  ['u'] = {60,64,64,32,124},
+  ['v'] = {28,32,64,32,28},
+  ['w'] = {60,64,48,64,60},
+  ['x'] = {68,36,124,72,68},
+  ['y'] = {12,80,80,60,0},
+  ['z'] = {68,100,84,76,68},
+  ['{'] = {0,8,54,65,0},
+  ['|'] = {0,0,119,0,0},
+  ['}'] = {0,65,54,8,0},
+  ['~'] = {8,4,8,16,8},
+};
+
+letterCode['('] = {0,60,66,129,0}
+letterCode[')'] = {0,129,66,60,0}
+letterCode[','] = {0,176,112,0,0}
+letterCode[';'] = {0,182,118,0,0}
+letterCode['['] = {0,255,129,129,0}
+letterCode[']'] = {0,129,129,255,0}
+letterCode['_'] = {128,128,128,128,128}
+letterCode['g'] = {24,164,164,124,0}
+letterCode['j'] = {64,128,132,125,0}
+letterCode['p'] = {252,36,36,36,24}
+letterCode['q'] = {24,36,36,252,128}
+letterCode['y'] = {12,80,80,60,0}
+letterCode['{'] = {0,24,102,129,0}
+letterCode['}'] = {0,129,102,24,0}
+
+
+local function HugeLetter(letter)
+  if letter==' ' then return {"  ", "  ", "  ", "  ", "  ", "  ", "  ", "  "} end
+  local code = letterCode[letter]  
+  local str = {"", "", "", "", "", "", "", ""}
+  for i=1,5 do
+    local n = code[i]
+    if n and n>0 then
+      for b=1,8 do
+        if bit32.btest(n, bit32.lshift(1, b-1)) then str[b] = str[b] .. letter else str[b] = str[b] .. ' ' end
+      end
+    end
+  end
+  return str
+end
+
+function HugeText(str)
+  local txt = {"", "", "", "", "", "", "", ""}
+  for i=1,string.len(str) do
+    local s = HugeLetter(str:sub(i,i))
+    for b=1,8 do
+      if i>1 then
+        txt[b] = txt[b] .. "   " .. s[b]
+      else
+        txt[b] = txt[b] .. s[b]
+      end
+    end
+  end
+  return txt
+end
+
+return HugeText

+ 37 - 0
test/html_esc.lua

@@ -0,0 +1,37 @@
+htmlEscape = {    "&#x263a;", "&#x263b;", "&#x2665;", "&#x2666;", "&#x2663;", "&#x2660;", "&#x2022;", -- ASCII 1-7 (symbols for control characters, see code page 437)
+      "&#x25d8;", "&#x25cb;", "&#x25d9;", "&#x2642;", "&#x2640;", "&#x266a;", "&#x266b;", "&#x263c;", -- ASCII 8-15
+      "&#x25ba;", "&#x25c4;", "&#x2195;", "&#x203c;", "&#x00b6;", "&#x00a7;", "&#x25ac;", "&#x21a8;", -- ASCII 16-23
+      "&#x2191;", "&#x2193;", "&#x21a8;", "&#x2190;", "&#x221f;", "&#x2192;", "&#x25b2;", "&#x25bc;", -- ASCII 24-31
+      " ",        "!",        "&quot;",   "#",        "$",        "%",        "&amp;",    "'",        -- ASCII 32-39
+      "(",        ")",        "*",        "+",        ",",        "-",        ".",        "/",        -- ASCII 40-47
+      "0",        "1",        "2",        "3",        "4",        "5",        "6",        "7",        -- ASCII 48-55
+      "8",        "9",        ":",        ";",        "&lt;",     "=",        "&gt;",     "?",        -- ASCII 56-63
+      "@",        "A",        "B",        "C",        "D",        "E",        "F",        "G",        -- ASCII 64-71
+      "H",        "I",        "J",        "K",        "L",        "M",        "N",        "O",        -- ASCII 72-79
+      "P",        "Q",        "R",        "S",        "T",        "U",        "V",        "W",        -- ASCII 80-87
+      "X",        "Y",        "Z",        "[",        "\\",       "]",        "^",        "_",        -- ASCII 88-95
+      "`",        "a",        "b",        "c",        "d",        "e",        "f",        "g",        -- ASCII 96-103
+      "h",        "i",        "j",        "k",        "l",        "m",        "n",        "o",        -- ASCII 104-111
+      "p",        "q",        "r",        "s",        "t",        "u",        "v",        "w",        -- ASCII 112-119
+      "x",        "y",        "z",        "{",        "|",        "}",        "~",        "&#x2302;", -- ASCII 120-127
+      "&Ccedil;", "&uuml;",   "&eacute;", "&acirc;",  "&auml;",   "&agrave;", "&aring;",  "&ccedil;", -- 128-135 (dos code page 850)
+      "&ecirc;",  "&euml;",   "&egrave;", "&iuml;",   "&icirc;",  "&igrave;", "&Auml;",   "&Aring;",  -- 136-143
+      "&Eacute;", "&aelig;",  "&AElig;",  "&ocirc;",  "&ouml;",   "&ograve;", "&ucirc;",  "&ugrave;", -- 144-151
+      "&yuml;",   "&Ouml;",   "&Uuml;",   "&oslash;", "&#x00a3;", "&Oslash;", "&#x00d7;", "&#x0192;", -- 152-159
+      "&aacute;", "&iacute;", "&oacute;", "&uacute;", "&ntilde;", "&Ntilde;", "&#x00aa;", "&#x00ba;", -- 160-167
+      "&#x00bf;", "&#x00ae;", "&#x00ac;", "&#x00bd;", "&#x00bc;", "&#x00a1;", "&#x00ab;", "&#x00bb;", -- 168-175
+      "&#x2591;", "&#x2592;", "&#x2593;", "&#x2502;", "&#x2524;", "&Aacute;", "&Acirc;",  "&Agrave;", -- 176-183
+      "&#x00a9;", "&#x2563;", "&#x2551;", "&#x2557;", "&#x255d;", "&cent;",   "&#x00a5;", "&#x2510;", -- 184-191
+      "&#x2514;", "&#x2534;", "&#x252c;", "&#x251c;", "&#x2500;", "&#x253c;", "&atilde;", "&Atilde;", -- 192-199
+      "&#x255a;", "&#x2554;", "&#x2569;", "&#x2566;", "&#x2560;", "&#x2550;", "&#x256c;", "&#x00a4;", -- 200-207
+      "&eth;",    "&ETH;",    "&Ecirc;",  "&Euml;",   "&Egrave;", "&#x0131;", "&Iacute;", "&Icirc;",  -- 208-215
+      "&Iuml;",   "&#x2518;", "&#x250c;", "&#x2588;", "&#x2584;", "&#x00a6;", "&Igrave;", "&#x2580;", -- 216-223
+      "&Oacute;", "&szlig;",  "&Ocirc;",  "&Ograve;", "&otilde;", "&Otilde;", "&#x00b5;", "&thorn;",  -- 224-231
+      "&THORN;",  "&Uacute;", "&Ucirc;",  "&Ugrave;", "&yacute;", "&Yacute;", "&#x00af;", "&#x00b4;", -- 232-239
+      "&equiv;",  "&#x00b1;", "&#x2017;", "&#x00be;", "&#x00b6;", "&#x00a7;", "&#x00f7;", "&#x00b8;", -- 240-247
+      "&#x00b0;", "&#x00a8;", "&#x00b7;", "&#x00b9;", "&#x00b3;", "&#x00b2;", "&#x25a0;", "&#9633;",  -- 248-255 (use empty box for 255)
+};
+htmlEscape[0] = "&middot;" -- in this table, we use a 8 bit character set, where every has a different graphical representation
+
+-- the conversion table should work as a convertion function for strings as well
+setmetatable(htmlEscape, {__call = function (tab,str) return string.gsub(str, ".", function (c) return tab[c:byte()] end) end})

+ 69 - 0
test/page.lua

@@ -0,0 +1,69 @@
+mg.write("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n")
+
+mg.write([[
+<html><body>
+<p>This is another example of a Lua script, creating a web page served by the
+<a href="http://code.google.com/p/civetweb">Civetweb web server</a>.
+</p><p>
+The following features are available:
+<ul>
+]])
+
+  mg.write("<li>" .. _VERSION .. " server pages</li>")
+  if sqlite3 then
+    mg.write("<li>sqlite3 binding</li>")
+  end
+  if lfs then
+    mg.write("<li>lua file system</li>")
+  end
+
+  
+mg.write("</ul></p>\r\n")
+mg.write("<p> Today is " .. os.date("%A") .. "</p>\r\n")
+mg.write("<p> URI is " .. mg.request_info.uri .. "</p>\r\n")
+
+mg.write("<p>Database example:\r\n<pre>\r\n")
+
+  -- Open database
+  local db = sqlite3.open('requests.db')
+
+  -- Setup a trace callback, to show SQL statements we'll be executing.
+  -- db:trace(function(data, sql) mg.write('Executing: ', sql: '\n') end, nil)
+
+  -- Create a table if it is not created already
+  db:exec([[
+    CREATE TABLE IF NOT EXISTS requests (
+      id INTEGER PRIMARY KEY AUTOINCREMENT,
+      timestamp NOT NULL,
+      method NOT NULL,
+      uri NOT NULL,
+      addr
+    );
+  ]])
+
+  -- Add entry about this request
+  local stmt = db:prepare(
+    'INSERT INTO requests VALUES(NULL, datetime("now"), ?, ?, ?);');
+  stmt:bind_values(mg.request_info.request_method,
+                   mg.request_info.uri,
+                   mg.request_info.remote_port)
+  stmt:step()
+  stmt:finalize()
+
+  -- Show all previous records
+  mg.write('Previous requests:\n')
+  stmt = db:prepare('SELECT * FROM requests ORDER BY id DESC;')
+  while stmt:step() == sqlite3.ROW do
+    local v = stmt:get_values()
+    mg.write(v[1] .. ' ' .. v[2] .. ' ' .. v[3] .. ' '
+          .. v[4] .. ' ' .. v[5] .. '\n')
+  end
+
+  -- Close database
+  db:close()
+
+mg.write([[
+</pre>
+</p>
+</body></html>
+]])

+ 18 - 11
test/page2.lp

@@ -9,48 +9,55 @@
 The following features are available:
 <ul>
 <?
-  -- function in one Lua tag should still be available in the next one
-  function test(tab, name)
+  -- functions defubed in one Lua tag should still be available in the next one
+  function print_if_available(tab, name)
     if tab then
       mg.write("<li>" .. name .. "</li>\n")
     end
   end
+
   function recurse(tab)
     mg.write("<ul>\n")
-    for k,v in pairs(tab) do      
+    for k,v in pairs(tab) do
       if type(v) == "table" then
         mg.write("<li>" .. tostring(k) .. ":</li>\n")
         recurse(v)
       else
-        mg.write("<li>" .. tostring(k) .. " = " .. tostring(v) .. "</li>\n")        
+        mg.write("<li>" .. tostring(k) .. " = " .. tostring(v) .. "</li>\n")
       end
     end
     mg.write("</ul>\n")
   end
 ?>
 <?
+  -- Print Lua version and available libraries
   mg.write("<li>" .. _VERSION .. " with the following standard libraries</li>\n")
   mg.write("<ul>")
   libs = {"string", "math", "table", "io", "os", "bit32", "package", "coroutine", "debug"};
   for _,n in ipairs(libs) do
-    test(_G[n], n);
+    print_if_available(_G[n], n);
   end
   mg.write("</ul>\n")
-  test(sqlite3, "sqlite3 binding")
-  test(lfs,"lua file system")
-  
+  print_if_available(sqlite3, "sqlite3 binding")
+  print_if_available(lfs,"lua file system")
+
+  -- Print mg library
   libname = "mg"
-  test(_G[libname], libname .. " library")
+  print_if_available(_G[libname], libname .. " library")
   recurse(_G[libname])
+
+  -- Print connect function
+  print_if_available(connect, "connect function")
+
 ?>
 </ul></p>
 <p> Today is <? mg.write(os.date("%A")) ?>
 
 <p>
 <?
-  -- for k,v in pairs(_G) do mg.write(k, '\n') end  
+  -- for k,v in pairs(_G) do mg.write(k, '\n') end
 
-  if lfs then    
+  if lfs then
     mg.write("Files in " .. lfs.currentdir())
     mg.write("\n<ul>\n")
     for f in lfs.dir(".") do

+ 46 - 40
test/page2.lua

@@ -9,54 +9,60 @@ mg.write([[<html><body>
 The following features are available:
 <ul>
 ]])
-  -- function in one Lua tag should still be available in the next one
-  function test(tab, name)
-    if tab then
-      mg.write("<li>" .. name .. "</li>\n")
-    end
-  end
-  function recurse(tab)
-    mg.write("<ul>\n")
-    for k,v in pairs(tab) do      
-      if type(v) == "table" then
-        mg.write("<li>" .. tostring(k) .. ":</li>\n")
-        recurse(v)
-      else
-        mg.write("<li>" .. tostring(k) .. " = " .. tostring(v) .. "</li>\n")        
-      end
-    end
-    mg.write("</ul>\n")
+
+function print_if_available(tab, name)
+  if tab then
+    mg.write("<li>" .. name .. "</li>\n")
   end
+end
 
-  mg.write("<li>" .. _VERSION .. " with the following standard libraries</li>\n")
+function recurse(tab)
   mg.write("<ul>\n")
-  libs = {"string", "math", "table", "io", "os", "bit32", "package", "coroutine", "debug"};
-  for _,n in ipairs(libs) do
-    test(_G[n], n);
+  for k,v in pairs(tab) do
+    if type(v) == "table" then
+      mg.write("<li>" .. tostring(k) .. ":</li>\n")
+      recurse(v)
+    else
+      mg.write("<li>" .. tostring(k) .. " = " .. tostring(v) .. "</li>\n")
+    end
   end
   mg.write("</ul>\n")
-  test(sqlite3, "sqlite3 binding")
-  test(lfs,"lua file system")
-  
-  libname = "mg"
-  test(_G[libname], libname .. " library")
-  recurse(_G[libname])
+end
 
-  mg.write("</ul></p>\n");
-  mg.write("<p> Today is " .. os.date("%A") .. "</p>\n");
+-- Print Lua version and available libraries
+mg.write("<li>" .. _VERSION .. " with the following standard libraries</li>\n")
+mg.write("<ul>\n")
+libs = {"string", "math", "table", "io", "os", "bit32", "package", "coroutine", "debug"};
+for _,n in ipairs(libs) do
+  print_if_available(_G[n], n);
+end
+mg.write("</ul>\n")
+print_if_available(sqlite3, "sqlite3 binding")
+print_if_available(lfs, "lua file system")
 
- mg.write("<p>\n");
- 
- if lfs then    
-    mg.write("Files in " .. lfs.currentdir())
-    mg.write("\n<ul>\n")
-    for f in lfs.dir(".") do
-      mg.write("<li>" .. f .. "</li>\n")
-      local at = lfs.attributes(f);
-      recurse(at)
-    end
-    mg.write("</ul>\n")
+-- Print mg library
+libname = "mg"
+print_if_available(_G[libname], libname .. " library")
+recurse(_G[libname])
+
+-- Print connect function
+print_if_available(connect, "connect function")
+
+mg.write("</ul></p>\n");
+mg.write("<p> Today is " .. os.date("%A") .. "</p>\n");
+
+mg.write("<p>\n");
+
+ if lfs then
+  mg.write("Files in " .. lfs.currentdir())
+  mg.write("\n<ul>\n")
+  for f in lfs.dir(".") do
+    mg.write("<li>" .. f .. "</li>\n")
+    local at = lfs.attributes(f);
+    recurse(at)
   end
+  mg.write("</ul>\n")
+end
 
 mg.write([[
 </p>

+ 34 - 0
test/page3.lua

@@ -0,0 +1,34 @@
+-- This test checks if a query string has been given.
+-- It sends the file identified by the query string.
+-- Do not use it in a real server in this way!
+
+if not mg.request_info.query_string then
+    mg.write("HTTP/1.0 200 OK\r\n")
+    mg.write("Connection: close\r\n")
+    mg.write("Content-Type: text/html; charset=utf-8\r\n")
+    mg.write("\r\n")
+    mg.write("<html><head><title>Civetweb Lua script test page 3</title></head>\r\n")
+    mg.write("<body>No query string!</body></html>\r\n")
+elseif mg.request_info.query_string:match("/") or mg.request_info.query_string:match("\\") then
+    mg.write("HTTP/1.0 403 Forbidden\r\n")
+    mg.write("Connection: close\r\n")
+    mg.write("Content-Type: text/html; charset=utf-8\r\n")
+    mg.write("\r\n")
+    mg.write("<html><head><title>Civetweb Lua script test page 3</title></head>\r\n")
+    mg.write("<body>No access!</body></html>\r\n")
+else
+    file = mg.get_var(mg.request_info.query_string, "file");
+    if not file then
+        mg.write("HTTP/1.0 400 Bad Request\r\n")
+        mg.write("Connection: close\r\n")
+        mg.write("Content-Type: text/html; charset=utf-8\r\n")
+        mg.write("\r\n")
+        mg.write("<html>\r\n<head><title>Civetweb Lua script test page 3</title></head>\r\n")
+        mg.write("<body>\r\nQuery string does not contain a 'file' variable.<br>\r\n")
+        mg.write("Try <a href=\"?file=page3.lua&somevar=something\">?file=page3.lua&somevar=something</a>\r\n")
+        mg.write("</body>\r\n</html>\r\n")
+    else
+        filename = mg.document_root .. "/" .. file
+        mg.send_file(filename)
+    end
+end

+ 128 - 0
test/page4.lua

@@ -0,0 +1,128 @@
+-- This test checks the Lua functions:
+-- get_var, get_cookie, md5, url_encode
+
+now = os.time()
+cookie_name = "civetweb-test-page4"
+
+if mg.request_info.http_headers.Cookie then
+   cookie_value = tonumber(mg.get_cookie(mg.request_info.http_headers.Cookie, cookie_name))
+end
+
+mg.write("HTTP/1.0 200 OK\r\n")
+mg.write("Connection: close\r\n")
+mg.write("Content-Type: text/html; charset=utf-8\r\n")
+if not cookie_value then
+    mg.write("Set-Cookie: " .. cookie_name .. "=" .. tostring(now) .. "\r\n")
+end
+mg.write("\r\n")
+
+mg.write("<html>\r\n<head><title>Civetweb Lua script test page 4</title></head>\r\n<body>\r\n")
+mg.write("<p>Test of Civetweb Lua Functions:</p>\r\n");
+mg.write("<pre>\r\n");
+
+-- get_var of query_string
+mg.write("get_var test (check query string):\r\n")
+if not mg.request_info.query_string then
+    mg.write("  No query string. You may try <a href='?a=a1&amp;junk&amp;b=b2&amp;cc=cNotSet&amp;d=a, b and d should be set&amp;z=z'>this example</a>.\r\n")
+else
+    for _,var in ipairs({'a','b','c','d'}) do
+       value = mg.get_var(mg.request_info.query_string, var);
+       if value then
+         mg.write("  Variable " .. var .. ": value " .. value .. "\r\n");
+       else
+         mg.write("  Variable " .. var .. " not set\r\n");
+       end
+    end
+end
+mg.write("\r\n")
+
+-- md5
+mg.write("MD5 test:\r\n")
+test_string = "abcd\0efgh"
+mg.write("  String with embedded 0, length " .. string.len(test_string))
+test_md5 = mg.md5(test_string)
+mg.write(", MD5 " .. test_md5 .. "\r\n")
+if mg.md5("") == "d41d8cd98f00b204e9800998ecf8427e" then
+    mg.write("  MD5 of empty string OK\r\n")
+else
+    mg.write("  Error: MD5 of empty string NOT OK\r\n")
+end
+if mg.md5("The quick brown fox jumps over the lazy dog.") == "e4d909c290d0fb1ca068ffaddf22cbd0" then
+    mg.write("  MD5 of test string OK\r\n")
+else
+    mg.write("  Error: MD5 of test string NOT OK\r\n")
+end
+mg.write("\r\n")
+
+-- get_cookie
+mg.write("Cookie test:\r\n")
+if not cookie_value then
+    mg.write("  Cookie not set yet. Please reload the page.\r\n")
+else
+    mg.write("  Cookie set to " .. cookie_value .. "\r\n")
+    mg.write("  You visited this page " .. os.difftime(now, cookie_value) .. " seconds before.\r\n")
+end
+mg.write("\r\n")
+
+-- test 'require' of other Lua scripts
+mg.write("require test\r\n")
+script_path = mg.request_info.script_name:match("(.*)page%d*.lua")
+if type(script_path)=='string' then
+    package.path = script_path .. "?.lua;" .. package.path
+    mg.write("  Lua search path: " .. package.path .. "\r\n")
+    require "html_esc"
+    require "require_test"
+    if htmlEscape then
+      for i=0,15 do
+        mg.write("  ")
+        for j=0,15 do
+            mg.write(tostring(htmlEscape[16*i+j]))
+        end
+        mg.write("\r\n")
+      end
+    else
+      mg.write("  'require' test failed (htmlEscape)\r\n")
+    end
+    if HugeText then
+      mg.write("\r\n")
+      local ht = HugeText(os.date("%a %b. %d"))
+      for i=1,#ht do
+        mg.write("  " .. ht[i] .. "\r\n")
+      end
+    else
+      mg.write("  'require' test failed (HugeText)\r\n")
+    end
+else
+    mg.write("  name match failed\r\n")
+end
+mg.write("\r\n")
+
+-- url_encode
+mg.write("URL encode/decode test:\r\n")
+if mg.url_encode("") == "" then
+    mg.write("  url_encode of empty string OK\r\n")
+else
+    mg.write("  Error: url_encode of empty string NOT OK\r\n")
+end
+raw_string = [[ !"#$%&'()*+,-./0123456789:;<=>?@]]
+mg.write("  original string: " .. htmlEscape(raw_string) .. "\r\n")
+url_string = mg.url_encode(raw_string):upper()
+ref_string = "%20!%22%23%24%25%26'()*%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40" -- from http://www.w3schools.com/tags/ref_urlencode.asp
+mg.write("  mg-url:        " .. htmlEscape(url_string) .. "\r\n")
+mg.write("  reference url: " .. htmlEscape(ref_string) .. "\r\n")
+dec_url_string = mg.url_decode(url_string)
+dec_ref_string = mg.url_decode(ref_string)
+mg.write("  decoded mg-url:        " .. htmlEscape(dec_url_string) .. "\r\n")
+mg.write("  decoded reference url: " .. htmlEscape(dec_ref_string) .. "\r\n")
+dec_url_string = mg.url_decode(url_string, false)
+dec_ref_string = mg.url_decode(ref_string, false)
+mg.write("  decoded mg-url:        " .. htmlEscape(dec_url_string) .. "\r\n")
+mg.write("  decoded reference url: " .. htmlEscape(dec_ref_string) .. "\r\n")
+dec_url_string = mg.url_decode(url_string, true)
+dec_ref_string = mg.url_decode(ref_string, true)
+mg.write("  decoded mg-url:        " .. htmlEscape(dec_url_string) .. "\r\n")
+mg.write("  decoded reference url: " .. htmlEscape(dec_ref_string) .. "\r\n")
+mg.write("\r\n")
+
+-- end of page
+mg.write("</pre>\r\n</body>\r\n</html>\r\n")

+ 31 - 0
test/page_keep_alive.lua

@@ -0,0 +1,31 @@
+-- Set keep_alive. The return value specifies if this is possible at all.
+canKeepAlive = mg.keep_alive(true)
+
+if canKeepAlive then
+    -- Create the entire response in a string variable first. Content-Length will be set to the length of this string.
+    reply = [[
+        <html><body>
+        <p>This is a Lua script supporting html keep-alive with the <a href="http://code.google.com/p/civetweb">Civetweb web server</a>.</p>
+        <p>It works by setting the Content-Length header field properly.
+        </body></html>
+    ]]
+else
+    reply = "<html><body>Keep alive not possible!</body></html>"
+end
+
+-- First send the http headers
+mg.write("HTTP/1.1 200 OK\r\n")
+mg.write("Content-Type: text/html\r\n")
+mg.write("Date: " .. os.date("!%a, %d %b %Y %H:%M:%S") .. " GMT\r\n")
+
+if canKeepAlive then
+    mg.write("Content-Length: " .. tostring(string.len(reply)) .. "\r\n")
+    mg.write("Connection: keep-alive\r\n")
+else
+    mg.write("Connection: close\r\n")
+end
+mg.write("\r\n")
+
+-- Finally send the content
+mg.write(reply)
+

+ 65 - 0
test/page_keep_alive_chunked.lua

@@ -0,0 +1,65 @@
+-- Set keep_alive. The return value specifies if this is possible at all.
+canKeepAlive = mg.keep_alive(true)
+now = os.date("!%a, %d %b %Y %H:%M:%S")
+
+-- First send the http headers
+mg.write("HTTP/1.1 200 OK\r\n")
+mg.write("Content-Type: text/html\r\n")
+mg.write("Date: " .. now .. " GMT\r\n")
+mg.write("Last-Modified: " .. now .. " GMT\r\n")
+if not canKeepAlive then
+    mg.write("Connection: close\r\n")
+    mg.write("\r\n")
+    mg.write("<html><body>Keep alive not possible!</body></html>")
+    return
+end
+if mg.request_info.http_version ~= "1.1" then
+    -- wget will use HTTP/1.0 and Connection: keep-alive, so chunked transfer is not possible
+    mg.write("Connection: close\r\n")
+    mg.write("\r\n")
+    mg.write("<html><body>Chunked transfer is only possible for HTTP/1.1 requests!</body></html>")
+    mg.keep_alive(false)
+    return
+end
+
+-- use chunked encoding (http://www.jmarshall.com/easy/http/#http1.1c2)
+mg.write("Cache-Control: max-age=0, must-revalidate\r\n")
+--mg.write("Cache-Control: no-cache\r\n")
+--mg.write("Cache-Control: no-store\r\n")
+mg.write("Connection: keep-alive\r\n")
+mg.write("Transfer-Encoding: chunked\r\n")
+mg.write("\r\n")
+
+-- function to send a chunk
+function send(str)
+    local len = string.len(str)
+    mg.write(string.format("%x\r\n", len))
+    mg.write(str.."\r\n")
+end
+
+-- send the chunks
+send("<html>")
+send("<head><title>Civetweb Lua script chunked transfer test page</title></head>")
+send("<body>\n")
+
+fileCnt = 0
+if lfs then
+    send("Files in " .. lfs.currentdir())
+    send('\n<table border="1">\n')
+    send('<tr><th>name</th><th>type</th><th>size</th></tr>\n')
+    for f in lfs.dir(".") do
+        local at = lfs.attributes(f);
+        if at then
+          send('<tr><td>' .. f .. '</td><td>' .. at.mode .. '</td><td>' .. at.size .. '</td></tr>\n')
+        end
+        fileCnt = fileCnt + 1
+    end
+    send("</table>\n")
+end
+
+send(fileCnt .. " entries (" .. now .. " GMT)\n")
+send("</body>")
+send("</html>")
+
+-- end
+send("")

+ 2 - 0
test/require_test.lua

@@ -0,0 +1,2 @@
+require 'html_esc'
+require 'HugeText'

+ 74 - 0
test/resource_script_demo.lua

@@ -0,0 +1,74 @@
+-- This is a Lua script that handles sub-resources, e.g. resource_script_demo.lua/path/file.ext
+
+scriptUri = "resource_script_demo.lua"
+envVar = "resource_script_demo_storage"
+
+resourcedir = os.getenv(envVar)
+method = mg.request_info.request_method:upper()
+
+if resourcedir then
+  attr = lfs.attributes(resourcedir)
+end
+
+if (not mg.request_info.uri:find(scriptUri)) or (not resourcedir) or (not attr) or (attr.mode~="directory") then
+    mg.write("HTTP/1.0 500 OK\r\n")
+    mg.write("Connection: close\r\n")
+    mg.write("Content-Type: text/html; charset=utf-8\r\n")
+    mg.write("\r\n")
+    mg.write("<html><head><title>Civetweb Lua script resource handling test</title></head>\r\n")
+    mg.write("<body>\r\nServer error.<br>\r\n")
+    mg.write("The server admin must make sure this script is available as URI " .. scriptUri .. "<br>\r\n")
+    mg.write("The server admin must set the environment variable " .. envVar .. " to a directory.<br>\r\n")
+    mg.write("</body>\r\n</html>\r\n")
+    return
+end
+subresource = mg.request_info.uri:match(scriptUri .. "/(.*)")
+
+if not subresource then
+    if method=="GET" then
+        mg.write("HTTP/1.0 200 OK\r\n")
+        mg.write("Connection: close\r\n")
+        mg.write("Content-Type: text/html; charset=utf-8\r\n")
+        mg.write("\r\n")
+        mg.write("<html><head><title>Civetweb Lua script resource handling test</title></head>\r\n")
+        mg.write("<body>No resource specified.</body></html>\r\n")
+    else
+        mg.write("HTTP/1.0 405 Method Not Allowed\r\n")
+        mg.write("Connection: close\r\n")
+        mg.write("Content-Type: text/html; charset=utf-8\r\n")
+        mg.write("\r\n")
+        mg.write("<html><head><title>Civetweb Lua script resource handling test</title></head>\r\n")
+        mg.write("<body>Method not allowed.</body></html>\r\n")
+    end
+    return
+end
+
+
+if method=="GET" then
+    file = resourcedir .. subresource
+    if lfs.attributes(file) then
+        mg.send_file(file)
+    else
+        mime = mg.get_mime_type(file)
+        mg.write("HTTP/1.0 404 Not Found\r\n")
+        mg.write("Connection: close\r\n")
+        mg.write("Content-Type: text/html; charset=utf-8\r\n")
+        mg.write("\r\n")
+        mg.write("<html><head><title>Civetweb Lua script resource handling test</title></head>\r\n")
+        mg.write("<body>Resource of type \"" .. mime .. "\" not found.</body></html>\r\n")
+    end
+    return
+end
+
+
+
+
+
+-- Any other method
+mg.write("HTTP/1.0 405 Method Not Allowed\r\n")
+mg.write("Connection: close\r\n")
+mg.write("Content-Type: text/html; charset=utf-8\r\n")
+mg.write("\r\n")
+mg.write("<html><head><title>Civetweb Lua script resource handling test</title></head>\r\n")
+mg.write("<body>Method not allowed.</body></html>\r\n")
+