Browse Source

Merge pull request #37 from bel2125/master

Fixes for the Lua integration + Websocket for Lua
sunsetbrew 11 years ago
parent
commit
f45df8cebf

+ 1 - 2
VS2012/ex_websocket/ex_websocket.vcxproj

@@ -27,7 +27,6 @@
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
     <ConfigurationType>Application</ConfigurationType>
     <ConfigurationType>Application</ConfigurationType>
     <UseDebugLibraries>true</UseDebugLibraries>
     <UseDebugLibraries>true</UseDebugLibraries>
-    <PlatformToolset>v110_xp</PlatformToolset>
     <CharacterSet>Unicode</CharacterSet>
     <CharacterSet>Unicode</CharacterSet>
   </PropertyGroup>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
@@ -39,7 +38,6 @@
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
     <ConfigurationType>Application</ConfigurationType>
     <ConfigurationType>Application</ConfigurationType>
     <UseDebugLibraries>false</UseDebugLibraries>
     <UseDebugLibraries>false</UseDebugLibraries>
-    <PlatformToolset>v110_xp</PlatformToolset>
     <WholeProgramOptimization>true</WholeProgramOptimization>
     <WholeProgramOptimization>true</WholeProgramOptimization>
     <CharacterSet>Unicode</CharacterSet>
     <CharacterSet>Unicode</CharacterSet>
   </PropertyGroup>
   </PropertyGroup>
@@ -150,6 +148,7 @@
     <ClInclude Include="..\..\include\civetweb.h" />
     <ClInclude Include="..\..\include\civetweb.h" />
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
+    <ClCompile Include="..\..\examples\websocket\WebSockCallbacks.c" />
     <ClCompile Include="..\..\src\civetweb.c" />
     <ClCompile Include="..\..\src\civetweb.c" />
     <ClCompile Include="..\..\examples\websocket\websocket.c" />
     <ClCompile Include="..\..\examples\websocket\websocket.c" />
   </ItemGroup>
   </ItemGroup>

+ 1 - 1
examples/websocket/Makefile

@@ -7,7 +7,7 @@
 
 
 
 
 PROG = websocket
 PROG = websocket
-SRC = websocket.c
+SRC = WebSockCallbacks.c websocket.c
 
 
 TOP = ../..
 TOP = ../..
 CIVETWEB_LIB = libcivetweb.a
 CIVETWEB_LIB = libcivetweb.a

+ 209 - 0
examples/websocket/WebSockCallbacks.c

@@ -0,0 +1,209 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <time.h>
+#include "WebSockCallbacks.h"
+
+#ifdef _WIN32
+#include <Windows.h>
+typedef HANDLE pthread_mutex_t;
+static int pthread_mutex_init(pthread_mutex_t *mutex, void *unused) {
+    unused = NULL;
+    *mutex = CreateMutex(NULL, FALSE, NULL);
+    return *mutex == NULL ? -1 : 0;
+}
+
+static int pthread_mutex_destroy(pthread_mutex_t *mutex) {
+    return CloseHandle(*mutex) == 0 ? -1 : 0;
+}
+
+static int pthread_mutex_lock(pthread_mutex_t *mutex) {
+    return WaitForSingleObject(*mutex, INFINITE) == WAIT_OBJECT_0? 0 : -1;
+}
+
+static int pthread_mutex_unlock(pthread_mutex_t *mutex) {
+    return ReleaseMutex(*mutex) == 0 ? -1 : 0;
+}
+#define mg_sleep(x) Sleep(x)
+#else
+#include <unistd.h>
+#include <pthread.h>
+#define mg_sleep(x) usleep((x) * 1000)
+#endif
+
+
+typedef struct tWebSockInfo {
+    int webSockState;
+    unsigned long initId;
+    struct mg_connection *conn;
+} tWebSockInfo;
+
+static pthread_mutex_t sMutex;
+
+#define MAX_NUM_OF_WEBSOCKS (256)
+static tWebSockInfo *socketList[MAX_NUM_OF_WEBSOCKS];
+
+
+static void send_to_all_websockets(const char * data, int data_len) {
+
+    int i;
+
+    for (i=0;i<MAX_NUM_OF_WEBSOCKS;i++) {
+        if (socketList[i] && (socketList[i]->webSockState==2)) {
+            mg_websocket_write(socketList[i]->conn, WEBSOCKET_OPCODE_TEXT, data, data_len);
+        }
+    }
+}
+
+
+void websocket_ready_handler(struct mg_connection *conn) {
+
+    int i;
+    struct mg_request_info * rq = mg_get_request_info(conn);
+    tWebSockInfo * wsock = malloc(sizeof(tWebSockInfo));
+    assert(wsock);
+    wsock->webSockState = 0;
+    rq->conn_data = wsock;
+
+    pthread_mutex_lock(&sMutex);
+    for (i=0;i<MAX_NUM_OF_WEBSOCKS;i++) {
+        if (0==socketList[i]) {
+            socketList[i] = wsock;
+            wsock->conn = conn;
+            wsock->webSockState = 1;
+            break;
+        }
+    }
+    pthread_mutex_unlock(&sMutex);
+}
+
+
+static void websocket_done(tWebSockInfo * wsock) {
+    int i;
+    if (wsock) {
+        wsock->webSockState = 99;
+        for (i=0;i<MAX_NUM_OF_WEBSOCKS;i++) {
+            if (wsock==socketList[i]) {
+                socketList[i] = 0;
+                break;
+            }
+        }
+        free(wsock);
+    }
+}
+
+
+int websocket_data_handler(struct mg_connection *conn, int flags, char *data, size_t data_len) {
+    struct mg_request_info * rq = mg_get_request_info(conn);
+    tWebSockInfo * wsock = (tWebSockInfo*)rq->conn_data;
+    char msg[128];
+
+    pthread_mutex_lock(&sMutex);
+    if (flags==136) {
+        // close websock
+        websocket_done(wsock);
+        rq->conn_data = 0;
+        pthread_mutex_unlock(&sMutex);
+        return 1;
+    }
+    if ((data_len>=5) && (data_len<100) && (flags==129) || (flags==130)) {
+
+        // init command
+        if ((wsock->webSockState==1) && (!memcmp(data,"init ",5))) {
+            char * chk;
+            unsigned long gid;
+            memcpy(msg,data+5,data_len-5);
+            msg[data_len-5]=0;
+            gid = strtoul(msg,&chk,10);
+            wsock->initId = gid;
+            if (gid>0 && chk!=NULL && *chk==0) {
+                wsock->webSockState = 2;
+            }
+            pthread_mutex_unlock(&sMutex);
+            return 1;
+        }
+
+        // chat message
+        if ((wsock->webSockState==2) && (!memcmp(data,"msg ",4))) {
+            send_to_all_websockets(data, data_len);
+            pthread_mutex_unlock(&sMutex);
+            return 1;
+        }
+    }
+
+    // keep alive
+    if ((data_len==4) && !memcmp(data,"ping",4)) {
+        pthread_mutex_unlock(&sMutex);
+        return 1;
+    }
+
+    pthread_mutex_unlock(&sMutex);
+    return 0;
+}
+
+
+void connection_close_handler(struct mg_connection *conn) {
+    struct mg_request_info * rq = mg_get_request_info(conn);
+    tWebSockInfo * wsock = (tWebSockInfo*)rq->conn_data;
+
+    pthread_mutex_lock(&sMutex);
+    websocket_done(wsock);
+    rq->conn_data = 0;
+    pthread_mutex_unlock(&sMutex);
+}
+
+
+static int runLoop = 0;
+
+static void * eventMain(void * _ignored) {
+    int i;
+    char msg[256];
+
+    runLoop = 1;
+    while (runLoop) {
+        time_t t = time(0);
+        struct tm * timestr = localtime(&t);
+        sprintf(msg,"title %s",asctime(timestr));
+
+        pthread_mutex_lock(&sMutex);
+        for (i=0;i<MAX_NUM_OF_WEBSOCKS;i++) {
+            if (socketList[i] && (socketList[i]->webSockState==2)) {
+                mg_websocket_write(socketList[i]->conn, WEBSOCKET_OPCODE_TEXT, msg, strlen(msg));
+            }
+        }
+        pthread_mutex_unlock(&sMutex);
+
+        mg_sleep(1000);
+    }
+
+    return _ignored;
+}
+
+void websock_send_broadcast(const char * data, int data_len) {
+
+    char buffer[260];
+
+    if (data_len<=256) {
+        strcpy(buffer, "msg ");
+        memcpy(buffer+4, data, data_len);
+        
+        pthread_mutex_lock(&sMutex);        
+        send_to_all_websockets(buffer, data_len+4);
+        pthread_mutex_unlock(&sMutex);
+    }
+}
+
+void websock_init_lib(void) {
+
+    int ret;
+    ret = pthread_mutex_init(&sMutex, 0);
+    assert(ret==0);
+
+    memset(socketList,0,sizeof(socketList));
+
+    mg_start_thread(eventMain, 0);
+}
+
+void websock_exit_lib(void) {
+
+    runLoop = 0;
+}

+ 25 - 0
examples/websocket/WebSockCallbacks.h

@@ -0,0 +1,25 @@
+
+#ifndef WEBSOCKCALLBACKS_H_INCLUDED
+#define WEBSOCKCALLBACKS_H_INCLUDED
+
+#include "civetweb.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void websock_init_lib(void);
+void websock_exit_lib(void);
+
+void websock_send_broadcast(const char * data, int data_len);
+
+void websocket_ready_handler(struct mg_connection *conn);
+int websocket_data_handler(struct mg_connection *conn, int flags, char *data, size_t data_len);
+void connection_close_handler(struct mg_connection *conn);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 0 - 44
examples/websocket/docroot/index.html

@@ -1,44 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8" />
-<title>WebSocket Test</title>
-<script language="javascript" type="text/javascript">
-
-  var writeToScreen = function(message) {
-    var div = document.createElement('div');
-    div.innerHTML = message;
-    document.getElementById('output').appendChild(div);
-  };
-  window.onload = function() {
-    var url = 'ws://' + window.location.host + '/foo';
-    websocket = new WebSocket(url);
-    websocket.onopen = function(ev) {
-      writeToScreen('CONNECTED');
-      var message = 'Не всё подчиняется разуму. Но всё подчиняется упорству. ';
-      writeToScreen('SENT: ' + message);
-      websocket.send(message);
-    };
-    websocket.onclose = function(ev) {
-      writeToScreen('DISCONNECTED');
-    };
-    websocket.onmessage = function(ev) {
-      writeToScreen('<span style="color: blue;">RESPONSE: ' + ev.data +
-                    ' </span>');
-      websocket.send('exit');
-    };
-    websocket.onerror = function(ev) {
-      writeToScreen('<span style="color: red; ">ERROR: </span> ' + ev.data);
-    };
-  };
-</script>
-<style> div {font: small Verdana; } </style>
-<h2>Civetweb WebSocket Test</h2>
-
-  <div style="width: 400px; color: #aaa; padding: 1em; ">
-  This page code creates websocket to the URI "/foo",
-  sends a message to it, waits for the reply, then sends an "exit" message.
-  Server must echo all messages back, and terminate the conversation after
-  receiving the "exit" message.
-  </div>
-
-<div id="output"></div>
-</html>

+ 55 - 0
examples/websocket/websock.htm

@@ -0,0 +1,55 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+  <title>Test</title>
+  <script type='text/javascript' language="javascript">
+  <!--
+  var connection;
+  var keepAlive = false;
+
+  function webSockKeepAlive() {
+    if (keepAlive) {
+      connection.send('ping'); // Send the message 'ping' to the server
+      setTimeout("webSockKeepAlive()", 10000);
+    }
+  }
+
+  function load() {
+    connection = new WebSocket("ws://127.0.0.1/MyWebSock");
+
+    connection.onopen = function () {
+        var send = "init " + Math.round(Math.random()*4294967294+1);
+        console.log('Client: ' + send);
+        connection.send(send);
+        keepAlive = true;
+        webSockKeepAlive();
+      };
+
+    connection.onerror = function (error) {
+        keepAlive = false;
+        connection.close();
+        console.log('WebSocket error: ' + error);
+        alert("WebSocket error");
+      };
+
+    connection.onmessage = function (e) {
+        console.log('Server: ' + e.data);
+        if (e.data.substring(0,5) == "title") {window.document.title = e.data.substring(6);}
+        else if (e.data.substring(0,3) == "msg") {
+          var msgStr = document.getElementById('msg');
+          msgStr.innerHTML = msgStr.innerHTML + e.data.substring(4);
+        }        
+      };
+  }
+  //-->
+  </script>
+
+</head>
+<body onload="load()">
+  <input type="button" onclick="connection.send('msg A');" value="A"></button>
+  <input type="button" onclick="connection.send('msg B');" value="B"></button>
+  <input type="button" onclick="connection.send('msg C');" value="C"></button>
+  <input type="button" onclick="connection.send('msg D');" value="D"></button>
+  <b id="msg"></b>
+</body>
+</html>

+ 32 - 32
examples/websocket/websocket.c

@@ -1,47 +1,47 @@
-// Copyright (c) 2004-2012 Sergey Lyubka
-// This file is a part of civetweb project, http://github.com/sunsetbrew/civetweb
-
 #include <stdio.h>
 #include <stdio.h>
+#include <ctype.h>
 #include <string.h>
 #include <string.h>
+
 #include "civetweb.h"
 #include "civetweb.h"
+#include "WebSockCallbacks.h"
 
 
-static void websocket_ready_handler(struct mg_connection *conn)
+int main(void)
 {
 {
-    static const char *message = "server ready";
-    mg_websocket_write(conn, WEBSOCKET_OPCODE_TEXT, message, strlen(message));
-}
+    struct mg_context *ctx = 0;
+    struct mg_callbacks callback_funcs = {0};
+    char inbuf[4];
 
 
-// Arguments:
-//   flags: first byte of websocket frame, see websocket RFC,
-//          http://tools.ietf.org/html/rfc6455, section 5.2
-//   data, data_len: payload data. Mask, if any, is already applied.
-static int websocket_data_handler(struct mg_connection *conn, int flags,
-                                  char *data, size_t data_len)
-{
-    (void) flags; // Unused
-    mg_websocket_write(conn, WEBSOCKET_OPCODE_TEXT, data, data_len);
+    const char *server_options[] = {
+        /* document_root: The path to the test function websock.htm */
+        "document_root",     "../../examples/websocket",
 
 
-    // Returning zero means stoping websocket conversation.
-    // Close the conversation if client has sent us "exit" string.
-    return memcmp(data, "exit", 4);
-}
+        /* port: use http standard to match websocket url in websock.htm: ws://127.0.0.1/MyWebSock  */
+        /*       if the port is changed here, it needs to be changed in websock.htm as wenn         */
+        "listening_ports",   "80",
 
 
-int main(void)
-{
-    struct mg_context *ctx;
-    struct mg_callbacks callbacks;
-    const char *options[] = {
-        "listening_ports", "8080",
-        "document_root", "docroot",
         NULL
         NULL
     };
     };
 
 
-    memset(&callbacks, 0, sizeof(callbacks));
-    callbacks.websocket_ready = websocket_ready_handler;
-    callbacks.websocket_data = websocket_data_handler;
-    ctx = mg_start(&callbacks, NULL, options);
-    getchar();  // Wait until user hits "enter"
+    websock_init_lib();
+
+    callback_funcs.websocket_ready = websocket_ready_handler;
+    callback_funcs.websocket_data = websocket_data_handler;
+    callback_funcs.connection_close = connection_close_handler;
+    ctx = mg_start(&callback_funcs, NULL, server_options);
+
+    puts("Enter an (ASCII) character or * to exit:");
+    for (;;) {
+        fgets(inbuf, sizeof(inbuf), stdin);
+
+        if (inbuf[0]=='*') {
+           break;
+        }
+        inbuf[0] = toupper(inbuf[0]);
+        websock_send_broadcast(inbuf, 1);
+    }
+
     mg_stop(ctx);
     mg_stop(ctx);
+    websock_exit_lib();
 
 
     return 0;
     return 0;
 }
 }

+ 206 - 62
src/civetweb.c

@@ -518,6 +518,14 @@ enum {
     GLOBAL_PASSWORDS_FILE, INDEX_FILES, ENABLE_KEEP_ALIVE, ACCESS_CONTROL_LIST,
     GLOBAL_PASSWORDS_FILE, INDEX_FILES, ENABLE_KEEP_ALIVE, ACCESS_CONTROL_LIST,
     EXTRA_MIME_TYPES, LISTENING_PORTS, DOCUMENT_ROOT, SSL_CERTIFICATE,
     EXTRA_MIME_TYPES, LISTENING_PORTS, DOCUMENT_ROOT, SSL_CERTIFICATE,
     NUM_THREADS, RUN_AS_USER, REWRITE, HIDE_FILES, REQUEST_TIMEOUT,
     NUM_THREADS, RUN_AS_USER, REWRITE, HIDE_FILES, REQUEST_TIMEOUT,
+
+#if defined(USE_LUA)
+    LUA_SCRIPT_EXTENSIONS, LUA_SERVER_PAGE_EXTENSIONS,
+#endif
+#if defined(USE_WEBSOCKET)
+    WEBSOCKET_ROOT,
+#endif
+
     NUM_OPTIONS
     NUM_OPTIONS
 };
 };
 
 
@@ -535,7 +543,11 @@ static const char *config_options[] = {
     "error_log_file", NULL,
     "error_log_file", NULL,
     "global_auth_file", NULL,
     "global_auth_file", NULL,
     "index_files",
     "index_files",
-    "index.html,index.htm,index.cgi,index.shtml,index.php,index.lp",
+#ifdef USE_LUA
+    "index.html,index.htm,index.lp,index.lsp,index.lua,index.cgi,index.shtml,index.php",
+#else
+    "index.html,index.htm,index.cgi,index.shtml,index.php",
+#endif
     "enable_keep_alive", "no",
     "enable_keep_alive", "no",
     "access_control_list", NULL,
     "access_control_list", NULL,
     "extra_mime_types", NULL,
     "extra_mime_types", NULL,
@@ -547,6 +559,15 @@ static const char *config_options[] = {
     "url_rewrite_patterns", NULL,
     "url_rewrite_patterns", NULL,
     "hide_files_patterns", NULL,
     "hide_files_patterns", NULL,
     "request_timeout_ms", "30000",
     "request_timeout_ms", "30000",
+
+#if defined(USE_LUA)
+    "lua_script_pattern", "**.lua$",
+    "lua_server_page_pattern", "**.lp$|**.lsp$",
+#endif
+#if defined(USE_WEBSOCKET)
+    "websocket_root", NULL,
+#endif
+
     NULL
     NULL
 };
 };
 
 
@@ -610,6 +631,9 @@ struct mg_connection {
     int64_t last_throttle_bytes;/* Bytes sent this second */
     int64_t last_throttle_bytes;/* Bytes sent this second */
     pthread_mutex_t mutex;      /* Used by mg_lock/mg_unlock to ensure atomic
     pthread_mutex_t mutex;      /* Used by mg_lock/mg_unlock to ensure atomic
                                    transmissions for websockets */
                                    transmissions for websockets */
+#if defined(USE_LUA) && defined(USE_WEBSOCKET)
+    void * lua_websocket_state; /* Lua_State for a websocket connection */
+#endif
 };
 };
 
 
 static pthread_key_t sTlsKey;  /* Thread local storage index */
 static pthread_key_t sTlsKey;  /* Thread local storage index */
@@ -629,6 +653,10 @@ struct de {
     struct file file;
     struct file file;
 };
 };
 
 
+#if defined(USE_WEBSOCKET)
+static int is_websocket_request(const struct mg_connection *conn);
+#endif
+
 const char **mg_get_valid_option_names(void)
 const char **mg_get_valid_option_names(void)
 {
 {
     return config_options;
     return config_options;
@@ -709,13 +737,27 @@ static void sockaddr_to_string(char *buf, size_t len,
               (void *) &usa->sin.sin_addr :
               (void *) &usa->sin.sin_addr :
               (void *) &usa->sin6.sin6_addr, buf, len);
               (void *) &usa->sin6.sin6_addr, buf, len);
 #elif defined(_WIN32)
 #elif defined(_WIN32)
-    /* Only Windoze Vista (and newer) have inet_ntop() */
+    /* Only Windows Vista (and newer) have inet_ntop() */
     strncpy(buf, inet_ntoa(usa->sin.sin_addr), len);
     strncpy(buf, inet_ntoa(usa->sin.sin_addr), len);
 #else
 #else
     inet_ntop(usa->sa.sa_family, (void *) &usa->sin.sin_addr, buf, len);
     inet_ntop(usa->sa.sa_family, (void *) &usa->sin.sin_addr, buf, len);
 #endif
 #endif
 }
 }
 
 
+/* Convert time_t to a string. According to RFC2616, Sec 14.18, this must be included in all responses other than 100, 101, 5xx. */
+static void gmt_time_string(char *buf, size_t buf_len, time_t *t)
+{
+    struct tm *tm;
+
+    tm = gmtime(t);
+    if (tm != NULL) {
+        strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S GMT", tm);
+    } else {
+        strncpy(buf, "Thu, 01 Jan 1970 00:00:00 GMT", buf_len);
+        buf[buf_len - 1] = '\0';
+    }
+}
+
 /* Print error message to the opened error log stream. */
 /* Print error message to the opened error log stream. */
 void mg_cry(struct mg_connection *conn, const char *fmt, ...)
 void mg_cry(struct mg_connection *conn, const char *fmt, ...)
 {
 {
@@ -1072,12 +1114,16 @@ static void send_http_error(struct mg_connection *conn, int status,
     char buf[MG_BUF_LEN];
     char buf[MG_BUF_LEN];
     va_list ap;
     va_list ap;
     int len = 0;
     int len = 0;
+    char date[64];
+    time_t curtime = time(NULL);
 
 
     conn->status_code = status;
     conn->status_code = status;
     if (conn->ctx->callbacks.http_error == NULL ||
     if (conn->ctx->callbacks.http_error == NULL ||
         conn->ctx->callbacks.http_error(conn, status)) {
         conn->ctx->callbacks.http_error(conn, status)) {
         buf[0] = '\0';
         buf[0] = '\0';
 
 
+        gmt_time_string(date, sizeof(date), &curtime);
+
         /* Errors 1xx, 204 and 304 MUST NOT send a body */
         /* Errors 1xx, 204 and 304 MUST NOT send a body */
         if (status > 199 && status != 204 && status != 304) {
         if (status > 199 && status != 204 && status != 304) {
             len = mg_snprintf(conn, buf, sizeof(buf), "Error %d: %s", status, reason);
             len = mg_snprintf(conn, buf, sizeof(buf), "Error %d: %s", status, reason);
@@ -1090,9 +1136,11 @@ static void send_http_error(struct mg_connection *conn, int status,
         DEBUG_TRACE(("[%s]", buf));
         DEBUG_TRACE(("[%s]", buf));
 
 
         mg_printf(conn, "HTTP/1.1 %d %s\r\n"
         mg_printf(conn, "HTTP/1.1 %d %s\r\n"
-                  "Content-Length: %d\r\n"
-                  "Connection: %s\r\n\r\n", status, reason, len,
-                  suggest_connection_header(conn));
+                        "Content-Length: %d\r\n"
+                        "Date: %s\r\n"
+                        "Connection: %s\r\n\r\n",
+                        status, reason, len, date,
+                        suggest_connection_header(conn));
         conn->num_bytes_sent += mg_printf(conn, "%s", buf);
         conn->num_bytes_sent += mg_printf(conn, "%s", buf);
     }
     }
 }
 }
@@ -2199,7 +2247,8 @@ int mg_get_cookie(const char *cookie_header, const char *var_name,
 }
 }
 
 
 static void convert_uri_to_file_name(struct mg_connection *conn, char *buf,
 static void convert_uri_to_file_name(struct mg_connection *conn, char *buf,
-                                     size_t buf_len, struct file *filep)
+                                     size_t buf_len, struct file *filep,
+                                     int * is_script_ressource)
 {
 {
     struct vec a, b;
     struct vec a, b;
     const char *rewrite, *uri = conn->request_info.uri,
     const char *rewrite, *uri = conn->request_info.uri,
@@ -2209,6 +2258,14 @@ static void convert_uri_to_file_name(struct mg_connection *conn, char *buf,
     char gz_path[PATH_MAX];
     char gz_path[PATH_MAX];
     char const* accept_encoding;
     char const* accept_encoding;
 
 
+    *is_script_ressource = 0;
+
+#if defined(USE_WEBSOCKET)
+    if (is_websocket_request(conn) && conn->ctx->config[WEBSOCKET_ROOT]) {
+        root = conn->ctx->config[WEBSOCKET_ROOT];
+    }
+#endif
+
     /* Using buf_len - 1 because memmove() for PATH_INFO may shift part
     /* Using buf_len - 1 because memmove() for PATH_INFO may shift part
        of the path one byte on the right.
        of the path one byte on the right.
        If document_root is NULL, leave the file empty. */
        If document_root is NULL, leave the file empty. */
@@ -2247,9 +2304,14 @@ static void convert_uri_to_file_name(struct mg_connection *conn, char *buf,
     for (p = buf + strlen(buf); p > buf + 1; p--) {
     for (p = buf + strlen(buf); p > buf + 1; p--) {
         if (*p == '/') {
         if (*p == '/') {
             *p = '\0';
             *p = '\0';
-            if (match_prefix(conn->ctx->config[CGI_EXTENSIONS],
-                             (int)strlen(conn->ctx->config[CGI_EXTENSIONS]), buf) > 0 &&
-                mg_stat(conn, buf, filep)) {
+            if ((match_prefix(conn->ctx->config[CGI_EXTENSIONS],
+                              (int)strlen(conn->ctx->config[CGI_EXTENSIONS]), buf) > 0
+#ifdef USE_LUA
+                 ||
+                 match_prefix(conn->ctx->config[LUA_SCRIPT_EXTENSIONS],
+                              (int)strlen(conn->ctx->config[LUA_SCRIPT_EXTENSIONS]), buf) > 0
+#endif
+                ) && mg_stat(conn, buf, filep)) {
                 /* Shift PATH_INFO block one character right, e.g.
                 /* Shift PATH_INFO block one character right, e.g.
                     "/x.cgi/foo/bar\x00" => "/x.cgi\x00/foo/bar\x00"
                     "/x.cgi/foo/bar\x00" => "/x.cgi\x00/foo/bar\x00"
                    conn->path_info is pointing to the local variable "path"
                    conn->path_info is pointing to the local variable "path"
@@ -2259,6 +2321,7 @@ static void convert_uri_to_file_name(struct mg_connection *conn, char *buf,
                 memmove(p + 2, p + 1, strlen(p + 1) + 1);  /* +1 is for
                 memmove(p + 2, p + 1, strlen(p + 1) + 1);  /* +1 is for
                                                               trailing \0 */
                                                               trailing \0 */
                 p[1] = '/';
                 p[1] = '/';
+                *is_script_ressource = 1;
                 break;
                 break;
             } else {
             } else {
                 *p = '/';
                 *p = '/';
@@ -2766,12 +2829,21 @@ static int check_authorization(struct mg_connection *conn, const char *path)
 
 
 static void send_authorization_request(struct mg_connection *conn)
 static void send_authorization_request(struct mg_connection *conn)
 {
 {
+    char date[64];
+    time_t curtime = time(NULL);
+
     conn->status_code = 401;
     conn->status_code = 401;
+    conn->must_close = 1;
+
+    gmt_time_string(date, sizeof(date), &curtime);
+
     mg_printf(conn,
     mg_printf(conn,
               "HTTP/1.1 401 Unauthorized\r\n"
               "HTTP/1.1 401 Unauthorized\r\n"
+              "Date: %s\r\n"
+              "Connection: %s\r\n"
               "Content-Length: 0\r\n"
               "Content-Length: 0\r\n"
-              "WWW-Authenticate: Digest qop=\"auth\", "
-              "realm=\"%s\", nonce=\"%lu\"\r\n\r\n",
+              "WWW-Authenticate: Digest qop=\"auth\", realm=\"%s\", nonce=\"%lu\"\r\n\r\n",
+              date, suggest_connection_header(conn),
               conn->ctx->config[AUTHENTICATION_DOMAIN],
               conn->ctx->config[AUTHENTICATION_DOMAIN],
               (unsigned long) time(NULL));
               (unsigned long) time(NULL));
 }
 }
@@ -3121,6 +3193,8 @@ static void handle_directory_request(struct mg_connection *conn,
 {
 {
     int i, sort_direction;
     int i, sort_direction;
     struct dir_scan_data data = { NULL, 0, 128 };
     struct dir_scan_data data = { NULL, 0, 128 };
+    char date[64];
+    time_t curtime = time(NULL);
 
 
     if (!scan_directory(conn, dir, &data, dir_scan_callback)) {
     if (!scan_directory(conn, dir, &data, dir_scan_callback)) {
         send_http_error(conn, 500, "Cannot open directory",
         send_http_error(conn, 500, "Cannot open directory",
@@ -3128,14 +3202,17 @@ static void handle_directory_request(struct mg_connection *conn,
         return;
         return;
     }
     }
 
 
+    gmt_time_string(date, sizeof(date), &curtime);
+
     sort_direction = conn->request_info.query_string != NULL &&
     sort_direction = conn->request_info.query_string != NULL &&
                      conn->request_info.query_string[1] == 'd' ? 'a' : 'd';
                      conn->request_info.query_string[1] == 'd' ? 'a' : 'd';
 
 
     conn->must_close = 1;
     conn->must_close = 1;
-    mg_printf(conn, "%s",
-              "HTTP/1.1 200 OK\r\n"
-              "Connection: close\r\n"
-              "Content-Type: text/html; charset=utf-8\r\n\r\n");
+    mg_printf(conn, "HTTP/1.1 200 OK\r\n"
+                    "Date: %s\r\n"
+                    "Connection: close\r\n"
+                    "Content-Type: text/html; charset=utf-8\r\n\r\n",
+                    date);
 
 
     conn->num_bytes_sent += mg_printf(conn,
     conn->num_bytes_sent += mg_printf(conn,
                                       "<html><head><title>Index of %s</title>"
                                       "<html><head><title>Index of %s</title>"
@@ -3218,19 +3295,6 @@ static int parse_range_header(const char *header, int64_t *a, int64_t *b)
     return sscanf(header, "bytes=%" INT64_FMT "-%" INT64_FMT, a, b);
     return sscanf(header, "bytes=%" INT64_FMT "-%" INT64_FMT, a, b);
 }
 }
 
 
-static void gmt_time_string(char *buf, size_t buf_len, time_t *t)
-{
-    struct tm *tm;
-
-    tm = gmtime(t);
-    if (tm != NULL) {
-        strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S GMT", tm);
-    } else {
-        strncpy(buf, "Thu, 01 Jan 1970 00:00:00 GMT", buf_len);
-        buf[buf_len - 1] = '\0';
-    }
-}
-
 static void construct_etag(char *buf, size_t buf_len,
 static void construct_etag(char *buf, size_t buf_len,
                            const struct file *filep)
                            const struct file *filep)
 {
 {
@@ -3927,6 +3991,9 @@ static void mkcol(struct mg_connection *conn, const char *path)
 {
 {
     int rc, body_len;
     int rc, body_len;
     struct de de;
     struct de de;
+    char date[64];
+    time_t curtime = time(NULL);
+
     memset(&de.file, 0, sizeof(de.file));
     memset(&de.file, 0, sizeof(de.file));
     if (!mg_stat(conn, path, &de.file)) {
     if (!mg_stat(conn, path, &de.file)) {
         mg_cry(conn, "%s: mg_stat(%s) failed: %s",
         mg_cry(conn, "%s: mg_stat(%s) failed: %s",
@@ -3950,7 +4017,9 @@ static void mkcol(struct mg_connection *conn, const char *path)
 
 
     if (rc == 0) {
     if (rc == 0) {
         conn->status_code = 201;
         conn->status_code = 201;
-        mg_printf(conn, "HTTP/1.1 %d Created\r\n\r\n", conn->status_code);
+        gmt_time_string(date, sizeof(date), &curtime);
+        mg_printf(conn, "HTTP/1.1 %d Created\r\nDate: %s\r\nContent-Length: 0\r\nConnection: %s\r\n\r\n",
+                  conn->status_code, date, suggest_connection_header(conn));
     } else if (rc == -1) {
     } else if (rc == -1) {
         if(errno == EEXIST)
         if(errno == EEXIST)
             send_http_error(conn, 405, "Method Not Allowed",
             send_http_error(conn, 405, "Method Not Allowed",
@@ -3973,11 +4042,15 @@ static void put_file(struct mg_connection *conn, const char *path)
     const char *range;
     const char *range;
     int64_t r1, r2;
     int64_t r1, r2;
     int rc;
     int rc;
+    char date[64];
+    time_t curtime = time(NULL);
 
 
     conn->status_code = mg_stat(conn, path, &file) ? 200 : 201;
     conn->status_code = mg_stat(conn, path, &file) ? 200 : 201;
 
 
     if ((rc = put_dir(conn, path)) == 0) {
     if ((rc = put_dir(conn, path)) == 0) {
-        mg_printf(conn, "HTTP/1.1 %d OK\r\n\r\n", conn->status_code);
+        gmt_time_string(date, sizeof(date), &curtime);
+        mg_printf(conn, "HTTP/1.1 %d OK\r\nDate: %s\r\nContent-Length: 0\r\nConnection: %s\r\n\r\n",
+                  conn->status_code, date, suggest_connection_header(conn));
     } else if (rc == -1) {
     } else if (rc == -1) {
         send_http_error(conn, 500, http_500_error,
         send_http_error(conn, 500, http_500_error,
                         "put_dir(%s): %s", path, strerror(ERRNO));
                         "put_dir(%s): %s", path, strerror(ERRNO));
@@ -3996,8 +4069,9 @@ static void put_file(struct mg_connection *conn, const char *path)
         if (!forward_body_data(conn, file.fp, INVALID_SOCKET, NULL)) {
         if (!forward_body_data(conn, file.fp, INVALID_SOCKET, NULL)) {
             conn->status_code = 500;
             conn->status_code = 500;
         }
         }
-        mg_printf(conn, "HTTP/1.1 %d OK\r\nContent-Length: 0\r\n\r\n",
-                  conn->status_code);
+        gmt_time_string(date, sizeof(date), &curtime);
+        mg_printf(conn, "HTTP/1.1 %d OK\r\nDate: %s\r\nContent-Length: 0\r\nConnection: %s\r\n\r\n",
+                  conn->status_code, date, suggest_connection_header(conn));
         mg_fclose(&file);
         mg_fclose(&file);
     }
     }
 }
 }
@@ -4147,16 +4221,21 @@ static void handle_ssi_file_request(struct mg_connection *conn,
                                     const char *path)
                                     const char *path)
 {
 {
     struct file file = STRUCT_FILE_INITIALIZER;
     struct file file = STRUCT_FILE_INITIALIZER;
+    char date[64];
+    time_t curtime = time(NULL);
 
 
     if (!mg_fopen(conn, path, "rb", &file)) {
     if (!mg_fopen(conn, path, "rb", &file)) {
         send_http_error(conn, 500, http_500_error, "fopen(%s): %s", path,
         send_http_error(conn, 500, http_500_error, "fopen(%s): %s", path,
                         strerror(ERRNO));
                         strerror(ERRNO));
     } else {
     } else {
         conn->must_close = 1;
         conn->must_close = 1;
+        gmt_time_string(date, sizeof(date), &curtime);
         fclose_on_exec(&file, conn);
         fclose_on_exec(&file, conn);
         mg_printf(conn, "HTTP/1.1 200 OK\r\n"
         mg_printf(conn, "HTTP/1.1 200 OK\r\n"
-                  "Content-Type: text/html\r\nConnection: %s\r\n\r\n",
-                  suggest_connection_header(conn));
+                        "Date: %s\r\n"
+                        "Content-Type: text/html\r\n"
+                        "Connection: %s\r\n\r\n",
+                  date, suggest_connection_header(conn));
         send_ssi_file(conn, path, &file, 0);
         send_ssi_file(conn, path, &file, 0);
         mg_fclose(&file);
         mg_fclose(&file);
     }
     }
@@ -4164,11 +4243,19 @@ static void handle_ssi_file_request(struct mg_connection *conn,
 
 
 static void send_options(struct mg_connection *conn)
 static void send_options(struct mg_connection *conn)
 {
 {
+    char date[64];
+    time_t curtime = time(NULL);
+
     conn->status_code = 200;
     conn->status_code = 200;
+    conn->must_close = 1;
+    gmt_time_string(date, sizeof(date), &curtime);
 
 
-    mg_printf(conn, "%s", "HTTP/1.1 200 OK\r\n"
-              "Allow: GET, POST, HEAD, CONNECT, PUT, DELETE, OPTIONS, PROPFIND, MKCOL\r\n"
-              "DAV: 1\r\n\r\n");
+    mg_printf(conn, "HTTP/1.1 200 OK\r\n"
+                    "Date: %s\r\n"
+                    "Connection: %s\r\n"
+                    "Allow: GET, POST, HEAD, CONNECT, PUT, DELETE, OPTIONS, PROPFIND, MKCOL\r\n"
+                    "DAV: 1\r\n\r\n",
+                    date, suggest_connection_header(conn));
 }
 }
 
 
 /* Writes PROPFIND properties for a collection element */
 /* Writes PROPFIND properties for a collection element */
@@ -4210,12 +4297,18 @@ static void handle_propfind(struct mg_connection *conn, const char *path,
                             struct file *filep)
                             struct file *filep)
 {
 {
     const char *depth = mg_get_header(conn, "Depth");
     const char *depth = mg_get_header(conn, "Depth");
+    char date[64];
+    time_t curtime = time(NULL);
+
+    gmt_time_string(date, sizeof(date), &curtime);
 
 
     conn->must_close = 1;
     conn->must_close = 1;
     conn->status_code = 207;
     conn->status_code = 207;
     mg_printf(conn, "HTTP/1.1 207 Multi-Status\r\n"
     mg_printf(conn, "HTTP/1.1 207 Multi-Status\r\n"
-              "Connection: close\r\n"
-              "Content-Type: text/xml; charset=utf-8\r\n\r\n");
+                    "Date: %s\r\n"
+                    "Connection: %s\r\n"
+                    "Content-Type: text/xml; charset=utf-8\r\n\r\n",
+                    date, suggest_connection_header(conn));
 
 
     conn->num_bytes_sent += mg_printf(conn,
     conn->num_bytes_sent += mg_printf(conn,
                                       "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
                                       "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
@@ -4244,6 +4337,10 @@ void mg_unlock(struct mg_connection* conn)
     (void) pthread_mutex_unlock(&conn->mutex);
     (void) pthread_mutex_unlock(&conn->mutex);
 }
 }
 
 
+#ifdef USE_LUA
+#include "mod_lua.inl"
+#endif /* USE_LUA */
+
 #if defined(USE_WEBSOCKET)
 #if defined(USE_WEBSOCKET)
 
 
 /* START OF SHA-1 code
 /* START OF SHA-1 code
@@ -4608,7 +4705,14 @@ static void read_websocket(struct mg_connection *conn)
             /* Exit the loop if callback signalled to exit,
             /* Exit the loop if callback signalled to exit,
                or "connection close" opcode received. */
                or "connection close" opcode received. */
             if ((conn->ctx->callbacks.websocket_data != NULL &&
             if ((conn->ctx->callbacks.websocket_data != NULL &&
+#ifdef USE_LUA
+                 (conn->lua_websocket_state == NULL) &&
+#endif
                  !conn->ctx->callbacks.websocket_data(conn, mop, data, data_len)) ||
                  !conn->ctx->callbacks.websocket_data(conn, mop, data, data_len)) ||
+#ifdef USE_LUA
+                (conn->lua_websocket_state &&
+                 !lua_websocket_data(conn, mop, data, data_len)) ||
+#endif
                 (buf[0] & 0xf) == WEBSOCKET_OPCODE_CONNECTION_CLOSE) {  /* Opcode == 8, connection close */
                 (buf[0] & 0xf) == WEBSOCKET_OPCODE_CONNECTION_CLOSE) {  /* Opcode == 8, connection close */
                 break;
                 break;
             }
             }
@@ -4670,20 +4774,37 @@ int mg_websocket_write(struct mg_connection* conn, int opcode, const char* data,
     return retval;
     return retval;
 }
 }
 
 
-static void handle_websocket_request(struct mg_connection *conn)
+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");
     const char *version = mg_get_header(conn, "Sec-WebSocket-Version");
     if (version == NULL || strcmp(version, "13") != 0) {
     if (version == NULL || strcmp(version, "13") != 0) {
         send_http_error(conn, 426, "Upgrade Required", "%s", "Upgrade Required");
         send_http_error(conn, 426, "Upgrade Required", "%s", "Upgrade Required");
     } else if (conn->ctx->callbacks.websocket_connect != NULL &&
     } else if (conn->ctx->callbacks.websocket_connect != NULL &&
                conn->ctx->callbacks.websocket_connect(conn) != 0) {
                conn->ctx->callbacks.websocket_connect(conn) != 0) {
-        /* Callback has returned non-zero, do not proceed with handshake */
+        /* C callback has returned non-zero, do not proceed with handshake. */
+        /* The C callback is called before Lua and may prevent Lua from handling the websocket. */
     } else {
     } else {
-        send_websocket_handshake(conn);
-        if (conn->ctx->callbacks.websocket_ready != NULL) {
-            conn->ctx->callbacks.websocket_ready(conn);
+#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);
+            if (conn->lua_websocket_state) {
+                send_websocket_handshake(conn);
+                if (lua_websocket_ready(conn)) {
+                    read_websocket(conn);
+                }
+            }
+        } else
+#endif
+        {
+            /* No Lua websock script specified. */
+            send_websocket_handshake(conn);
+            if (conn->ctx->callbacks.websocket_ready != NULL) {
+                conn->ctx->callbacks.websocket_ready(conn);
+            }
+            read_websocket(conn);
         }
         }
-        read_websocket(conn);
     }
     }
 }
 }
 
 
@@ -4759,10 +4880,6 @@ static uint32_t get_remote_ip(const struct mg_connection *conn)
     return ntohl(* (uint32_t *) &conn->client.rsa.sin.sin_addr);
     return ntohl(* (uint32_t *) &conn->client.rsa.sin.sin_addr);
 }
 }
 
 
-#ifdef USE_LUA
-#include "mod_lua.inl"
-#endif /* USE_LUA */
-
 int mg_upload(struct mg_connection *conn, const char *destination_dir)
 int mg_upload(struct mg_connection *conn, const char *destination_dir)
 {
 {
     const char *content_type_header, *boundary_start;
     const char *content_type_header, *boundary_start;
@@ -5028,8 +5145,10 @@ static void handle_request(struct mg_connection *conn)
 {
 {
     struct mg_request_info *ri = &conn->request_info;
     struct mg_request_info *ri = &conn->request_info;
     char path[PATH_MAX];
     char path[PATH_MAX];
-    int uri_len, ssl_index;
+    int uri_len, ssl_index, is_script_resource;
     struct file file = STRUCT_FILE_INITIALIZER;
     struct file file = STRUCT_FILE_INITIALIZER;
+    char date[64];
+    time_t curtime = time(NULL);
 
 
     if ((conn->request_info.query_string = strchr(ri->uri, '?')) != NULL) {
     if ((conn->request_info.query_string = strchr(ri->uri, '?')) != NULL) {
         * ((char *) conn->request_info.query_string++) = '\0';
         * ((char *) conn->request_info.query_string++) = '\0';
@@ -5038,7 +5157,7 @@ static void handle_request(struct mg_connection *conn)
     mg_url_decode(ri->uri, uri_len, (char *) ri->uri, uri_len + 1, 0);
     mg_url_decode(ri->uri, uri_len, (char *) ri->uri, uri_len + 1, 0);
     remove_double_dots_and_double_slashes((char *) ri->uri);
     remove_double_dots_and_double_slashes((char *) ri->uri);
     path[0] = '\0';
     path[0] = '\0';
-    convert_uri_to_file_name(conn, path, sizeof(path), &file);
+    convert_uri_to_file_name(conn, path, sizeof(path), &file, &is_script_resource);
     conn->throttle = set_throttle(conn->ctx->config[THROTTLE],
     conn->throttle = set_throttle(conn->ctx->config[THROTTLE],
                                   get_remote_ip(conn), ri->uri);
                                   get_remote_ip(conn), ri->uri);
 
 
@@ -5049,7 +5168,7 @@ static void handle_request(struct mg_connection *conn)
     if (!conn->client.is_ssl && conn->client.ssl_redir &&
     if (!conn->client.is_ssl && conn->client.ssl_redir &&
         (ssl_index = get_first_ssl_listener_index(conn->ctx)) > -1) {
         (ssl_index = get_first_ssl_listener_index(conn->ctx)) > -1) {
         redirect_to_https_port(conn, ssl_index);
         redirect_to_https_port(conn, ssl_index);
-    } else if (!is_put_or_delete_request(conn) &&
+    } else if (!is_script_resource && !is_put_or_delete_request(conn) &&
                !check_authorization(conn, path)) {
                !check_authorization(conn, path)) {
         send_authorization_request(conn);
         send_authorization_request(conn);
     } else if (conn->ctx->callbacks.begin_request != NULL &&
     } else if (conn->ctx->callbacks.begin_request != NULL &&
@@ -5060,20 +5179,20 @@ static void handle_request(struct mg_connection *conn)
         /* Do nothing, callback has served the request */
         /* Do nothing, callback has served the request */
 #if defined(USE_WEBSOCKET)
 #if defined(USE_WEBSOCKET)
     } else if (is_websocket_request(conn)) {
     } else if (is_websocket_request(conn)) {
-        handle_websocket_request(conn);
+        handle_websocket_request(conn, path, is_script_resource);
 #endif
 #endif
-    } else if (!strcmp(ri->request_method, "OPTIONS")) {
+    } else if (!is_script_resource && !strcmp(ri->request_method, "OPTIONS")) {
         send_options(conn);
         send_options(conn);
     } else if (conn->ctx->config[DOCUMENT_ROOT] == NULL) {
     } else if (conn->ctx->config[DOCUMENT_ROOT] == NULL) {
         send_http_error(conn, 404, "Not Found", "Not Found");
         send_http_error(conn, 404, "Not Found", "Not Found");
-    } else if (is_put_or_delete_request(conn) &&
+    } else if (!is_script_resource && is_put_or_delete_request(conn) &&
                (is_authorized_for_put(conn) != 1)) {
                (is_authorized_for_put(conn) != 1)) {
         send_authorization_request(conn);
         send_authorization_request(conn);
-    } else if (!strcmp(ri->request_method, "PUT")) {
+    } else if (!is_script_resource && !strcmp(ri->request_method, "PUT")) {
         put_file(conn, path);
         put_file(conn, path);
-    } else if (!strcmp(ri->request_method, "MKCOL")) {
+    } else if (!is_script_resource && !strcmp(ri->request_method, "MKCOL")) {
         mkcol(conn, path);
         mkcol(conn, path);
-    } else if (!strcmp(ri->request_method, "DELETE")) {
+    } else if (!is_script_resource && !strcmp(ri->request_method, "DELETE")) {
         struct de de;
         struct de de;
         memset(&de.file, 0, sizeof(de.file));
         memset(&de.file, 0, sizeof(de.file));
         if(!mg_stat(conn, path, &de.file)) {
         if(!mg_stat(conn, path, &de.file)) {
@@ -5098,9 +5217,14 @@ static void handle_request(struct mg_connection *conn)
                must_hide_file(conn, path)) {
                must_hide_file(conn, path)) {
         send_http_error(conn, 404, "Not Found", "%s", "File not found");
         send_http_error(conn, 404, "Not Found", "%s", "File not found");
     } else if (file.is_directory && ri->uri[uri_len - 1] != '/') {
     } else if (file.is_directory && ri->uri[uri_len - 1] != '/') {
+        gmt_time_string(date, sizeof(date), &curtime);
         mg_printf(conn, "HTTP/1.1 301 Moved Permanently\r\n"
         mg_printf(conn, "HTTP/1.1 301 Moved Permanently\r\n"
-                  "Location: %s/\r\n\r\n", ri->uri);
-    } else if (!strcmp(ri->request_method, "PROPFIND")) {
+                        "Location: %s/\r\n"
+                        "Date: %s\r\n"
+                        "Content-Length: 0\r\n"
+                        "Connection: %s\r\n\r\n",
+                        ri->uri, date, suggest_connection_header(conn));
+    } else if (!is_script_resource && !strcmp(ri->request_method, "PROPFIND")) {
         handle_propfind(conn, path, &file);
         handle_propfind(conn, path, &file);
     } else if (file.is_directory &&
     } else if (file.is_directory &&
                !substitute_index_file(conn, path, sizeof(path), &file)) {
                !substitute_index_file(conn, path, sizeof(path), &file)) {
@@ -5111,13 +5235,22 @@ static void handle_request(struct mg_connection *conn)
                             "Directory listing denied");
                             "Directory listing denied");
         }
         }
 #ifdef USE_LUA
 #ifdef USE_LUA
-    } else if (match_prefix("**.lp$", 6, path) > 0) {
+    } else if (match_prefix(conn->ctx->config[LUA_SERVER_PAGE_EXTENSIONS],
+                            (int)strlen(conn->ctx->config[LUA_SERVER_PAGE_EXTENSIONS]),
+                            path) > 0) {
+        /* Lua server page: an SSI like page containing mostly plain html code plus some tags with server generated contents. */
         handle_lsp_request(conn, path, &file, NULL);
         handle_lsp_request(conn, path, &file, NULL);
+    } else if (match_prefix(conn->ctx->config[LUA_SCRIPT_EXTENSIONS],
+                            (int)strlen(conn->ctx->config[LUA_SCRIPT_EXTENSIONS]),
+                            path) > 0) {
+        /* Lua in-server module script: a CGI like script used to generate the entire reply. */
+        mg_exec_lua_script(conn, path, NULL);
 #endif
 #endif
 #if !defined(NO_CGI)
 #if !defined(NO_CGI)
     } else if (match_prefix(conn->ctx->config[CGI_EXTENSIONS],
     } else if (match_prefix(conn->ctx->config[CGI_EXTENSIONS],
                             (int)strlen(conn->ctx->config[CGI_EXTENSIONS]),
                             (int)strlen(conn->ctx->config[CGI_EXTENSIONS]),
                             path) > 0) {
                             path) > 0) {
+        /* TODO: check unsupported methods -> 501
         if (strcmp(ri->request_method, "POST") &&
         if (strcmp(ri->request_method, "POST") &&
             strcmp(ri->request_method, "HEAD") &&
             strcmp(ri->request_method, "HEAD") &&
             strcmp(ri->request_method, "GET")) {
             strcmp(ri->request_method, "GET")) {
@@ -5125,7 +5258,8 @@ static void handle_request(struct mg_connection *conn)
                             "Method %s is not implemented", ri->request_method);
                             "Method %s is not implemented", ri->request_method);
         } else {
         } else {
             handle_cgi_request(conn, path);
             handle_cgi_request(conn, path);
-        }
+        } */
+        handle_cgi_request(conn, path);
 #endif /* !NO_CGI */
 #endif /* !NO_CGI */
     } else if (match_prefix(conn->ctx->config[SSI_EXTENSIONS],
     } else if (match_prefix(conn->ctx->config[SSI_EXTENSIONS],
                             (int)strlen(conn->ctx->config[SSI_EXTENSIONS]),
                             (int)strlen(conn->ctx->config[SSI_EXTENSIONS]),
@@ -5572,6 +5706,12 @@ static void close_socket_gracefully(struct mg_connection *conn)
 
 
 static void close_connection(struct mg_connection *conn)
 static void close_connection(struct mg_connection *conn)
 {
 {
+#if defined(USE_LUA) && defined(USE_WEBSOCKET)
+    if (conn->lua_websocket_state) {
+        lua_websocket_close(conn);
+    }
+#endif
+
     /* call the connection_close callback if assigned */
     /* call the connection_close callback if assigned */
     if (conn->ctx->callbacks.connection_close != NULL)
     if (conn->ctx->callbacks.connection_close != NULL)
         conn->ctx->callbacks.connection_close(conn);
         conn->ctx->callbacks.connection_close(conn);
@@ -6164,6 +6304,10 @@ struct mg_context *mg_start(const struct mg_callbacks *callbacks,
     InitializeCriticalSection(&global_log_file_lock);
     InitializeCriticalSection(&global_log_file_lock);
 #endif /* _WIN32 && !__SYMBIAN32__ */
 #endif /* _WIN32 && !__SYMBIAN32__ */
 
 
+    /* Check if the config_options and the corresponding enum have compatible sizes. */
+    /* Could use static_assert, once it is verified that all compilers support this. */
+    assert(sizeof(config_options)/sizeof(config_options[0]) == NUM_OPTIONS*2+1);
+
     /* Allocate context and initialize reasonable general case defaults.
     /* Allocate context and initialize reasonable general case defaults.
        TODO(lsm): do proper error handling here. */
        TODO(lsm): do proper error handling here. */
     if ((ctx = (struct mg_context *) calloc(1, sizeof(*ctx))) == NULL) {
     if ((ctx = (struct mg_context *) calloc(1, sizeof(*ctx))) == NULL) {

+ 292 - 52
src/mod_lua.inl

@@ -1,9 +1,10 @@
 #include <lua.h>
 #include <lua.h>
 #include <lauxlib.h>
 #include <lauxlib.h>
+#include <setjmp.h>
 
 
 #ifdef _WIN32
 #ifdef _WIN32
 static void *mmap(void *addr, int64_t len, int prot, int flags, int fd,
 static void *mmap(void *addr, int64_t len, int prot, int flags, int fd,
-                  int offset)
+    int offset)
 {
 {
     HANDLE fh = (HANDLE) _get_osfhandle(fd);
     HANDLE fh = (HANDLE) _get_osfhandle(fd);
     HANDLE mh = CreateFileMapping(fh, 0, PAGE_READONLY, 0, 0, 0);
     HANDLE mh = CreateFileMapping(fh, 0, PAGE_READONLY, 0, 0, 0);
@@ -11,7 +12,12 @@ static void *mmap(void *addr, int64_t len, int prot, int flags, int fd,
     CloseHandle(mh);
     CloseHandle(mh);
     return p;
     return p;
 }
 }
-#define munmap(x, y)  UnmapViewOfFile(x)
+
+static void munmap(void *addr, int64_t length)
+{
+    UnmapViewOfFile(addr);
+}
+
 #define MAP_FAILED NULL
 #define MAP_FAILED NULL
 #define MAP_PRIVATE 0
 #define MAP_PRIVATE 0
 #define PROT_READ 0
 #define PROT_READ 0
@@ -21,32 +27,47 @@ static void *mmap(void *addr, int64_t len, int prot, int flags, int fd,
 
 
 static const char *LUASOCKET = "luasocket";
 static const char *LUASOCKET = "luasocket";
 
 
-// Forward declarations
+/* Forward declarations */
 static void handle_request(struct mg_connection *);
 static void handle_request(struct mg_connection *);
 static int handle_lsp_request(struct mg_connection *, const char *,
 static int handle_lsp_request(struct mg_connection *, const char *,
-                              struct file *, struct lua_State *);
+struct file *, struct lua_State *);
 
 
 static void reg_string(struct lua_State *L, const char *name, const char *val)
 static void reg_string(struct lua_State *L, const char *name, const char *val)
 {
 {
-    lua_pushstring(L, name);
-    lua_pushstring(L, val);
-    lua_rawset(L, -3);
+    if (name!=NULL && val!=NULL) {
+        lua_pushstring(L, name);
+        lua_pushstring(L, val);
+        lua_rawset(L, -3);
+    }
 }
 }
 
 
 static void reg_int(struct lua_State *L, const char *name, int val)
 static void reg_int(struct lua_State *L, const char *name, int val)
 {
 {
-    lua_pushstring(L, name);
-    lua_pushinteger(L, val);
-    lua_rawset(L, -3);
+    if (name!=NULL) {
+        lua_pushstring(L, name);
+        lua_pushinteger(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_function(struct lua_State *L, const char *name,
 static void reg_function(struct lua_State *L, const char *name,
-                         lua_CFunction func, struct mg_connection *conn)
+    lua_CFunction func, struct mg_connection *conn)
 {
 {
-    lua_pushstring(L, name);
-    lua_pushlightuserdata(L, conn);
-    lua_pushcclosure(L, func, 1);
-    lua_rawset(L, -3);
+    if (name!=NULL && func!=NULL) {
+        lua_pushstring(L, name);
+        lua_pushlightuserdata(L, conn);
+        lua_pushcclosure(L, func, 1);
+        lua_rawset(L, -3);
+    }
 }
 }
 
 
 static int lsp_sock_close(lua_State *L)
 static int lsp_sock_close(lua_State *L)
@@ -116,7 +137,7 @@ static int lsp_connect(lua_State *L)
 
 
     if (lua_isstring(L, -3) && lua_isnumber(L, -2) && lua_isnumber(L, -1)) {
     if (lua_isstring(L, -3) && lua_isnumber(L, -2) && lua_isnumber(L, -1)) {
         sock = conn2(NULL, lua_tostring(L, -3), (int) lua_tonumber(L, -2),
         sock = conn2(NULL, lua_tostring(L, -3), (int) lua_tonumber(L, -2),
-                     (int) lua_tonumber(L, -1), ebuf, sizeof(ebuf));
+            (int) lua_tonumber(L, -1), ebuf, sizeof(ebuf));
         if (sock == INVALID_SOCKET) {
         if (sock == INVALID_SOCKET) {
             return luaL_error(L, ebuf);
             return luaL_error(L, ebuf);
         } else {
         } else {
@@ -141,7 +162,7 @@ static int lsp_error(lua_State *L)
     return 0;
     return 0;
 }
 }
 
 
-// Silently stop processing chunks.
+/* Silently stop processing chunks. */
 static void lsp_abort(lua_State *L)
 static void lsp_abort(lua_State *L)
 {
 {
     int top = lua_gettop(L);
     int top = lua_gettop(L);
@@ -154,7 +175,7 @@ static void lsp_abort(lua_State *L)
 }
 }
 
 
 static int lsp(struct mg_connection *conn, const char *path,
 static int lsp(struct mg_connection *conn, const char *path,
-               const char *p, int64_t len, lua_State *L)
+    const char *p, int64_t len, lua_State *L)
 {
 {
     int i, j, pos = 0, lines = 1, lualines = 0;
     int i, j, pos = 0, lines = 1, lualines = 0;
     char chunkname[MG_BUF_LEN];
     char chunkname[MG_BUF_LEN];
@@ -171,10 +192,10 @@ static int lsp(struct mg_connection *conn, const char *path,
                     lua_pushlightuserdata(L, conn);
                     lua_pushlightuserdata(L, conn);
                     lua_pushcclosure(L, lsp_error, 1);
                     lua_pushcclosure(L, lsp_error, 1);
                     if (luaL_loadbuffer(L, p + (i + 2), j - (i + 2), chunkname)) {
                     if (luaL_loadbuffer(L, p + (i + 2), j - (i + 2), chunkname)) {
-                        // Syntax error or OOM. Error message is pushed on stack.
+                        /* Syntax error or OOM. Error message is pushed on stack. */
                         lua_pcall(L, 1, 0, 0);
                         lua_pcall(L, 1, 0, 0);
                     } else {
                     } else {
-                        // Success loading chunk. Call it.
+                        /* Success loading chunk. Call it. */
                         lua_pcall(L, 0, 0, 1);
                         lua_pcall(L, 0, 0, 1);
                     }
                     }
 
 
@@ -227,20 +248,20 @@ static int lsp_read(lua_State *L)
     return 1;
     return 1;
 }
 }
 
 
-// mg.include: Include another .lp file
+/* mg.include: Include another .lp file */
 static int lsp_include(lua_State *L)
 static int lsp_include(lua_State *L)
 {
 {
     struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1));
     struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1));
     struct file file = STRUCT_FILE_INITIALIZER;
     struct file file = STRUCT_FILE_INITIALIZER;
     if (handle_lsp_request(conn, lua_tostring(L, -1), &file, L)) {
     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.
+        /* 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);
         lsp_abort(L);
     }
     }
     return 0;
     return 0;
 }
 }
 
 
-// mg.cry: Log an error. Default value for mg.onerror.
+/* mg.cry: Log an error. Default value for mg.onerror. */
 static int lsp_cry(lua_State *L)
 static int lsp_cry(lua_State *L)
 {
 {
     struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1));
     struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1));
@@ -248,7 +269,7 @@ static int lsp_cry(lua_State *L)
     return 0;
     return 0;
 }
 }
 
 
-// mg.redirect: Redirect the request (internally).
+/* mg.redirect: Redirect the request (internally). */
 static int lsp_redirect(lua_State *L)
 static int lsp_redirect(lua_State *L)
 {
 {
     struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1));
     struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1));
@@ -258,12 +279,57 @@ static int lsp_redirect(lua_State *L)
     return 0;
     return 0;
 }
 }
 
 
-static void prepare_lua_environment(struct mg_connection *conn, lua_State *L)
+static int lwebsock_write(lua_State *L)
+{
+#ifdef USE_WEBSOCKET
+    int num_args = lua_gettop(L);
+    struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1));
+    const char *str;
+    size_t size;
+    int opcode = -1;
+
+    if (num_args == 1) {
+        if (lua_isstring(L, 1)) {
+            str = lua_tolstring(L, 1, &size);
+            mg_websocket_write(conn, WEBSOCKET_OPCODE_TEXT, str, size);
+        }
+    } else if (num_args == 2) {
+        if (lua_isnumber(L, 1)) {
+            opcode = (int)lua_tointeger(L, 1);
+        } else if (lua_isstring(L,1)) {
+            str = lua_tostring(L, 1);
+            if (!mg_strncasecmp(str, "text", 4)) opcode = WEBSOCKET_OPCODE_TEXT;
+            else if (!mg_strncasecmp(str, "bin", 3)) opcode = WEBSOCKET_OPCODE_BINARY;
+            else if (!mg_strncasecmp(str, "close", 5)) opcode = WEBSOCKET_OPCODE_CONNECTION_CLOSE;
+            else if (!mg_strncasecmp(str, "ping", 4)) opcode = WEBSOCKET_OPCODE_PING;
+            else if (!mg_strncasecmp(str, "pong", 4)) opcode = WEBSOCKET_OPCODE_PONG;
+            else if (!mg_strncasecmp(str, "cont", 4)) opcode = WEBSOCKET_OPCODE_CONTINUATION;
+        }
+        if (opcode>=0 && opcode<16 && lua_isstring(L, 2)) {
+            str = lua_tolstring(L, 2, &size);
+            mg_websocket_write(conn, WEBSOCKET_OPCODE_TEXT, str, size);
+        }
+    }
+#endif
+    return 0;
+}
+
+enum {
+    LUA_ENV_TYPE_LUA_SERVER_PAGE = 0,
+    LUA_ENV_TYPE_PLAIN_LUA_PAGE = 1,
+    LUA_ENV_TYPE_LUA_WEBSOCKET = 2,
+};
+
+static void prepare_lua_environment(struct mg_connection *conn, lua_State *L, const char *script_name, int lua_env_type)
 {
 {
     const struct mg_request_info *ri = mg_get_request_info(conn);
     const struct mg_request_info *ri = mg_get_request_info(conn);
-    extern void luaL_openlibs(lua_State *);
+    char src_addr[IP_ADDR_STR_LEN];
     int i;
     int i;
 
 
+    extern void luaL_openlibs(lua_State *);
+
+    sockaddr_to_string(src_addr, sizeof(src_addr), &conn->client.rsa);
+
     luaL_openlibs(L);
     luaL_openlibs(L);
 #ifdef USE_LUA_SQLITE3
 #ifdef USE_LUA_SQLITE3
     {
     {
@@ -287,40 +353,64 @@ static void prepare_lua_environment(struct mg_connection *conn, lua_State *L)
 
 
     if (conn == NULL) return;
     if (conn == NULL) return;
 
 
-    // Register mg module
+    /* Register mg module */
     lua_newtable(L);
     lua_newtable(L);
 
 
-    reg_function(L, "read", lsp_read, conn);
-    reg_function(L, "write", lsp_write, conn);
     reg_function(L, "cry", lsp_cry, conn);
     reg_function(L, "cry", lsp_cry, conn);
-    reg_function(L, "include", lsp_include, conn);
-    reg_function(L, "redirect", lsp_redirect, conn);
+
+    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);
+    }
+
+    if (lua_env_type==LUA_ENV_TYPE_LUA_SERVER_PAGE) {
+        reg_function(L, "include", lsp_include, conn);
+        reg_function(L, "redirect", lsp_redirect, conn);
+    }
+
+    if (lua_env_type==LUA_ENV_TYPE_LUA_WEBSOCKET) {
+        reg_function(L, "write", lwebsock_write, conn);
+    }
+
     reg_string(L, "version", CIVETWEB_VERSION);
     reg_string(L, "version", CIVETWEB_VERSION);
     reg_string(L, "document_root", conn->ctx->config[DOCUMENT_ROOT]);
     reg_string(L, "document_root", conn->ctx->config[DOCUMENT_ROOT]);
+    reg_string(L, "auth_domain", conn->ctx->config[AUTHENTICATION_DOMAIN]);
 
 
-    // Export request_info
+    /* Export request_info */
     lua_pushstring(L, "request_info");
     lua_pushstring(L, "request_info");
     lua_newtable(L);
     lua_newtable(L);
     reg_string(L, "request_method", ri->request_method);
     reg_string(L, "request_method", ri->request_method);
     reg_string(L, "uri", ri->uri);
     reg_string(L, "uri", ri->uri);
     reg_string(L, "http_version", ri->http_version);
     reg_string(L, "http_version", ri->http_version);
     reg_string(L, "query_string", ri->query_string);
     reg_string(L, "query_string", ri->query_string);
-    reg_int(L, "remote_ip", ri->remote_ip);
+    reg_int(L, "remote_ip", ri->remote_ip); /* remote_ip is deprecated, use remote_addr instead */
+    reg_string(L, "remote_addr", src_addr);
+    /* TODO: ip version */
     reg_int(L, "remote_port", ri->remote_port);
     reg_int(L, "remote_port", ri->remote_port);
     reg_int(L, "num_headers", ri->num_headers);
     reg_int(L, "num_headers", ri->num_headers);
+    reg_int(L, "server_port", ntohs(conn->client.lsa.sin.sin_port));
+
+    if (conn->request_info.remote_user != NULL) {
+        reg_string(L, "remote_user", conn->request_info.remote_user);
+        reg_string(L, "auth_type", "Digest");
+    }
+
     lua_pushstring(L, "http_headers");
     lua_pushstring(L, "http_headers");
     lua_newtable(L);
     lua_newtable(L);
     for (i = 0; i < ri->num_headers; i++) {
     for (i = 0; i < ri->num_headers; i++) {
         reg_string(L, ri->http_headers[i].name, ri->http_headers[i].value);
         reg_string(L, ri->http_headers[i].name, ri->http_headers[i].value);
     }
     }
     lua_rawset(L, -3);
     lua_rawset(L, -3);
-    lua_rawset(L, -3);
 
 
+    reg_boolean(L, "https", conn->ssl != NULL);
+    reg_string(L, "script_name", script_name);
+
+    lua_rawset(L, -3);
     lua_setglobal(L, "mg");
     lua_setglobal(L, "mg");
 
 
-    // Register default mg.onerror function
+    /* Register default mg.onerror function */
     IGNORE_UNUSED_RESULT(luaL_dostring(L, "mg.onerror = function(e) mg.write('\\nLua error:\\n', "
     IGNORE_UNUSED_RESULT(luaL_dostring(L, "mg.onerror = function(e) mg.write('\\nLua error:\\n', "
-                                       "debug.traceback(e, 1)) end"));
+        "debug.traceback(e, 1)) end"));
 }
 }
 
 
 static int lua_error_handler(lua_State *L)
 static int lua_error_handler(lua_State *L)
@@ -329,7 +419,7 @@ static int lua_error_handler(lua_State *L)
 
 
     lua_getglobal(L, "mg");
     lua_getglobal(L, "mg");
     if (!lua_isnil(L, -1)) {
     if (!lua_isnil(L, -1)) {
-        lua_getfield(L, -1, "write");   // call mg.write()
+        lua_getfield(L, -1, "write");   /* call mg.write() */
         lua_pushstring(L, error_msg);
         lua_pushstring(L, error_msg);
         lua_pushliteral(L, "\n");
         lua_pushliteral(L, "\n");
         lua_call(L, 2, 0);
         lua_call(L, 2, 0);
@@ -338,23 +428,23 @@ static int lua_error_handler(lua_State *L)
         printf("Lua error: [%s]\n", error_msg);
         printf("Lua error: [%s]\n", error_msg);
         IGNORE_UNUSED_RESULT(luaL_dostring(L, "print(debug.traceback(), '\\n')"));
         IGNORE_UNUSED_RESULT(luaL_dostring(L, "print(debug.traceback(), '\\n')"));
     }
     }
-    // TODO(lsm): leave the stack balanced
+    /* TODO(lsm): leave the stack balanced */
 
 
     return 0;
     return 0;
 }
 }
 
 
 void mg_exec_lua_script(struct mg_connection *conn, const char *path,
 void mg_exec_lua_script(struct mg_connection *conn, const char *path,
-                        const void **exports)
+    const void **exports)
 {
 {
     int i;
     int i;
     lua_State *L;
     lua_State *L;
 
 
     if (path != NULL && (L = luaL_newstate()) != NULL) {
     if (path != NULL && (L = luaL_newstate()) != NULL) {
-        prepare_lua_environment(conn, L);
+        prepare_lua_environment(conn, L, path, LUA_ENV_TYPE_PLAIN_LUA_PAGE);
         lua_pushcclosure(L, &lua_error_handler, 0);
         lua_pushcclosure(L, &lua_error_handler, 0);
 
 
-        lua_pushglobaltable(L);
         if (exports != NULL) {
         if (exports != NULL) {
+            lua_pushglobaltable(L);
             for (i = 0; exports[i] != NULL && exports[i + 1] != NULL; i += 2) {
             for (i = 0; exports[i] != NULL && exports[i + 1] != NULL; i += 2) {
                 lua_pushstring(L, exports[i]);
                 lua_pushstring(L, exports[i]);
                 lua_pushcclosure(L, (lua_CFunction) exports[i + 1], 0);
                 lua_pushcclosure(L, (lua_CFunction) exports[i + 1], 0);
@@ -368,10 +458,11 @@ void mg_exec_lua_script(struct mg_connection *conn, const char *path,
         lua_pcall(L, 0, 0, -2);
         lua_pcall(L, 0, 0, -2);
         lua_close(L);
         lua_close(L);
     }
     }
+    conn->must_close=1;
 }
 }
 
 
 static void lsp_send_err(struct mg_connection *conn, struct lua_State *L,
 static void lsp_send_err(struct mg_connection *conn, struct lua_State *L,
-                         const char *fmt, ...)
+    const char *fmt, ...)
 {
 {
     char buf[MG_BUF_LEN];
     char buf[MG_BUF_LEN];
     va_list ap;
     va_list ap;
@@ -390,32 +481,32 @@ static void lsp_send_err(struct mg_connection *conn, struct lua_State *L,
 }
 }
 
 
 static int handle_lsp_request(struct mg_connection *conn, const char *path,
 static int handle_lsp_request(struct mg_connection *conn, const char *path,
-                              struct file *filep, struct lua_State *ls)
+struct file *filep, struct lua_State *ls)
 {
 {
     void *p = NULL;
     void *p = NULL;
     lua_State *L = NULL;
     lua_State *L = NULL;
     int error = 1;
     int error = 1;
 
 
-    // We need both mg_stat to get file size, and mg_fopen to get fd
+    /* 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)) {
     if (!mg_stat(conn, path, filep) || !mg_fopen(conn, path, "r", filep)) {
         lsp_send_err(conn, ls, "File [%s] not found", path);
         lsp_send_err(conn, ls, "File [%s] not found", path);
     } else if (filep->membuf == NULL &&
     } else if (filep->membuf == NULL &&
-               (p = mmap(NULL, (size_t) filep->size, PROT_READ, MAP_PRIVATE,
-                         fileno(filep->fp), 0)) == MAP_FAILED) {
-        lsp_send_err(conn, ls, "mmap(%s, %zu, %d): %s", path, (size_t) filep->size,
-                     fileno(filep->fp), strerror(errno));
+        (p = mmap(NULL, (size_t) filep->size, PROT_READ, MAP_PRIVATE,
+        fileno(filep->fp), 0)) == MAP_FAILED) {
+            lsp_send_err(conn, ls, "mmap(%s, %zu, %d): %s", path, (size_t) filep->size,
+                fileno(filep->fp), strerror(errno));
     } else if ((L = ls != NULL ? ls : luaL_newstate()) == NULL) {
     } else if ((L = ls != NULL ? ls : luaL_newstate()) == NULL) {
         send_http_error(conn, 500, http_500_error, "%s", "luaL_newstate failed");
         send_http_error(conn, 500, http_500_error, "%s", "luaL_newstate failed");
     } else {
     } else {
-        // We're not sending HTTP headers here, Lua page must do it.
+        /* We're not sending HTTP headers here, Lua page must do it. */
         if (ls == NULL) {
         if (ls == NULL) {
-            prepare_lua_environment(conn, L);
+            prepare_lua_environment(conn, L, path, LUA_ENV_TYPE_LUA_SERVER_PAGE);
             if (conn->ctx->callbacks.init_lua != NULL) {
             if (conn->ctx->callbacks.init_lua != NULL) {
                 conn->ctx->callbacks.init_lua(conn, L);
                 conn->ctx->callbacks.init_lua(conn, L);
             }
             }
         }
         }
         error = lsp(conn, path, filep->membuf == NULL ? p : filep->membuf,
         error = lsp(conn, path, filep->membuf == NULL ? p : filep->membuf,
-                    filep->size, L);
+            filep->size, L);
     }
     }
 
 
     if (L != NULL && ls == NULL) lua_close(L);
     if (L != NULL && ls == NULL) lua_close(L);
@@ -424,3 +515,152 @@ static int handle_lsp_request(struct mg_connection *conn, const char *path,
     conn->must_close=1;
     conn->must_close=1;
     return error;
     return error;
 }
 }
+
+#ifdef USE_WEBSOCKET
+struct lua_websock_data {
+    lua_State *main;
+    lua_State *thread;
+};
+
+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;
+    }
+}
+
+static void * new_lua_websocket(const char * script, struct mg_connection *conn)
+{
+    struct lua_websock_data *lws_data;
+    int ok = 0;
+    int err, nargs;
+
+    assert(conn->lua_websocket_state == NULL);
+    lws_data = (struct lua_websock_data *) malloc(sizeof(*lws_data));
+
+    if (lws_data) {
+        lws_data->main = luaL_newstate();
+        if (lws_data->main) {
+            prepare_lua_environment(conn, lws_data->main, script, LUA_ENV_TYPE_LUA_WEBSOCKET);
+            if (conn->ctx->callbacks.init_lua != NULL) {
+                conn->ctx->callbacks.init_lua(conn, lws_data->main);
+            }
+            lws_data->thread = lua_newthread(lws_data->main);
+            err = luaL_loadfile(lws_data->thread, script);
+            if (err==LUA_OK) {
+                /* Activate the Lua script. */
+                err = lua_resume(lws_data->thread, NULL, 0);
+                if (err!=LUA_YIELD) {
+                    websock_cry(conn, err, lws_data->thread, __func__, "lua_resume");
+                } else {
+                    nargs = lua_gettop(lws_data->thread);
+                    ok = (nargs==1) && lua_isboolean(lws_data->thread, 1) && lua_toboolean(lws_data->thread, 1);
+                }
+            } else {
+                websock_cry(conn, err, lws_data->thread, __func__, "lua_loadfile");
+            }
+
+        } else {
+            mg_cry(conn, "%s: luaL_newstate failed", __func__);
+        }
+
+        if (!ok) {
+            if (lws_data->main) lua_close(lws_data->main);
+            free(lws_data);
+            lws_data=0;
+        }
+    }
+
+    return lws_data;
+}
+
+static int lua_websocket_data(struct mg_connection *conn, int bits, char *data, size_t data_len)
+{
+    struct lua_websock_data *lws_data = (struct lua_websock_data *)(conn->lua_websocket_state);
+    int err, nargs, ok=0, retry;
+    lua_Number delay;
+
+    assert(lws_data != NULL);
+    assert(lws_data->main != NULL);
+    assert(lws_data->thread != NULL);
+
+    do {
+        retry=0;
+        lua_pushboolean(lws_data->thread, 1);
+        if (bits > 0) {
+            lua_pushinteger(lws_data->thread, bits);
+            if (data) {
+                lua_pushlstring(lws_data->thread, data, data_len);
+                err = lua_resume(lws_data->thread, NULL, 3);
+            } else {
+                err = lua_resume(lws_data->thread, NULL, 2);
+            }
+        } else {
+            err = lua_resume(lws_data->thread, NULL, 1);
+        }
+
+        if (err!=LUA_YIELD) {
+            websock_cry(conn, err, lws_data->thread, __func__, "lua_resume");
+        } else {
+            nargs = lua_gettop(lws_data->thread);
+            ok = (nargs>=1) && lua_isboolean(lws_data->thread, 1) && lua_toboolean(lws_data->thread, 1);
+            delay = (nargs>=2) && lua_isnumber(lws_data->thread, 2) ? lua_tonumber(lws_data->thread, 2) : -1.0;
+            if (ok && delay>0) {
+                fd_set rfds;
+                struct timeval tv;
+
+                FD_ZERO(&rfds);
+                FD_SET(conn->client.sock, &rfds);
+
+                tv.tv_sec = (unsigned long)delay;
+                tv.tv_usec = (unsigned long)(((double)delay - (double)((unsigned long)delay))*1000000.0);
+                retry = (0==select(1, &rfds, NULL, NULL, &tv));
+            }
+        }
+    } while (retry);
+
+    return ok;
+}
+
+static int lua_websocket_ready(struct mg_connection *conn)
+{
+    return lua_websocket_data(conn, -1, NULL, 0);
+}
+
+static void lua_websocket_close(struct mg_connection *conn)
+{
+    struct lua_websock_data *lws_data = (struct lua_websock_data *)(conn->lua_websocket_state);
+    int err;
+
+    assert(lws_data != NULL);
+    assert(lws_data->main != NULL);
+    assert(lws_data->thread != NULL);
+
+    lua_pushboolean(lws_data->thread, 0);
+    err = lua_resume(lws_data->thread, NULL, 1);
+
+    lua_close(lws_data->main);
+    free(lws_data);
+    conn->lua_websocket_state = NULL;
+}
+#endif

File diff suppressed because it is too large
+ 623 - 593
src/third_party/sqlite3.c


+ 103 - 49
src/third_party/sqlite3.h

@@ -107,9 +107,9 @@ extern "C" {
 ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
 ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
 ** [sqlite_version()] and [sqlite_source_id()].
 ** [sqlite_version()] and [sqlite_source_id()].
 */
 */
-#define SQLITE_VERSION        "3.7.17"
-#define SQLITE_VERSION_NUMBER 3007017
-#define SQLITE_SOURCE_ID      "2013-05-20 00:56:22 118a3b35693b134d56ebd780123b7fd6f1497668"
+#define SQLITE_VERSION        "3.8.1"
+#define SQLITE_VERSION_NUMBER 3008001
+#define SQLITE_SOURCE_ID      "2013-10-17 12:57:35 c78be6d786c19073b3a6730dfe3fb1be54f5657a"
 
 
 /*
 /*
 ** CAPI3REF: Run-Time Library Version Numbers
 ** CAPI3REF: Run-Time Library Version Numbers
@@ -478,11 +478,15 @@ SQLITE_API int sqlite3_exec(
 #define SQLITE_IOERR_SEEK              (SQLITE_IOERR | (22<<8))
 #define SQLITE_IOERR_SEEK              (SQLITE_IOERR | (22<<8))
 #define SQLITE_IOERR_DELETE_NOENT      (SQLITE_IOERR | (23<<8))
 #define SQLITE_IOERR_DELETE_NOENT      (SQLITE_IOERR | (23<<8))
 #define SQLITE_IOERR_MMAP              (SQLITE_IOERR | (24<<8))
 #define SQLITE_IOERR_MMAP              (SQLITE_IOERR | (24<<8))
+#define SQLITE_IOERR_GETTEMPPATH       (SQLITE_IOERR | (25<<8))
+#define SQLITE_IOERR_CONVPATH          (SQLITE_IOERR | (26<<8))
 #define SQLITE_LOCKED_SHAREDCACHE      (SQLITE_LOCKED |  (1<<8))
 #define SQLITE_LOCKED_SHAREDCACHE      (SQLITE_LOCKED |  (1<<8))
 #define SQLITE_BUSY_RECOVERY           (SQLITE_BUSY   |  (1<<8))
 #define SQLITE_BUSY_RECOVERY           (SQLITE_BUSY   |  (1<<8))
+#define SQLITE_BUSY_SNAPSHOT           (SQLITE_BUSY   |  (2<<8))
 #define SQLITE_CANTOPEN_NOTEMPDIR      (SQLITE_CANTOPEN | (1<<8))
 #define SQLITE_CANTOPEN_NOTEMPDIR      (SQLITE_CANTOPEN | (1<<8))
 #define SQLITE_CANTOPEN_ISDIR          (SQLITE_CANTOPEN | (2<<8))
 #define SQLITE_CANTOPEN_ISDIR          (SQLITE_CANTOPEN | (2<<8))
 #define SQLITE_CANTOPEN_FULLPATH       (SQLITE_CANTOPEN | (3<<8))
 #define SQLITE_CANTOPEN_FULLPATH       (SQLITE_CANTOPEN | (3<<8))
+#define SQLITE_CANTOPEN_CONVPATH       (SQLITE_CANTOPEN | (4<<8))
 #define SQLITE_CORRUPT_VTAB            (SQLITE_CORRUPT | (1<<8))
 #define SQLITE_CORRUPT_VTAB            (SQLITE_CORRUPT | (1<<8))
 #define SQLITE_READONLY_RECOVERY       (SQLITE_READONLY | (1<<8))
 #define SQLITE_READONLY_RECOVERY       (SQLITE_READONLY | (1<<8))
 #define SQLITE_READONLY_CANTLOCK       (SQLITE_READONLY | (2<<8))
 #define SQLITE_READONLY_CANTLOCK       (SQLITE_READONLY | (2<<8))
@@ -499,6 +503,7 @@ SQLITE_API int sqlite3_exec(
 #define SQLITE_CONSTRAINT_VTAB         (SQLITE_CONSTRAINT | (9<<8))
 #define SQLITE_CONSTRAINT_VTAB         (SQLITE_CONSTRAINT | (9<<8))
 #define SQLITE_NOTICE_RECOVER_WAL      (SQLITE_NOTICE | (1<<8))
 #define SQLITE_NOTICE_RECOVER_WAL      (SQLITE_NOTICE | (1<<8))
 #define SQLITE_NOTICE_RECOVER_ROLLBACK (SQLITE_NOTICE | (2<<8))
 #define SQLITE_NOTICE_RECOVER_ROLLBACK (SQLITE_NOTICE | (2<<8))
+#define SQLITE_WARNING_AUTOINDEX       (SQLITE_WARNING | (1<<8))
 
 
 /*
 /*
 ** CAPI3REF: Flags For File Open Operations
 ** CAPI3REF: Flags For File Open Operations
@@ -1612,27 +1617,27 @@ struct sqlite3_mem_methods {
 ** function must be threadsafe. </dd>
 ** function must be threadsafe. </dd>
 **
 **
 ** [[SQLITE_CONFIG_URI]] <dt>SQLITE_CONFIG_URI
 ** [[SQLITE_CONFIG_URI]] <dt>SQLITE_CONFIG_URI
-** <dd> This option takes a single argument of type int. If non-zero, then
+** <dd>^(This option takes a single argument of type int. If non-zero, then
 ** URI handling is globally enabled. If the parameter is zero, then URI handling
 ** URI handling is globally enabled. If the parameter is zero, then URI handling
-** is globally disabled. If URI handling is globally enabled, all filenames
+** is globally disabled.)^ ^If URI handling is globally enabled, all filenames
 ** passed to [sqlite3_open()], [sqlite3_open_v2()], [sqlite3_open16()] or
 ** passed to [sqlite3_open()], [sqlite3_open_v2()], [sqlite3_open16()] or
 ** specified as part of [ATTACH] commands are interpreted as URIs, regardless
 ** specified as part of [ATTACH] commands are interpreted as URIs, regardless
 ** of whether or not the [SQLITE_OPEN_URI] flag is set when the database
 ** of whether or not the [SQLITE_OPEN_URI] flag is set when the database
-** connection is opened. If it is globally disabled, filenames are
+** connection is opened. ^If it is globally disabled, filenames are
 ** only interpreted as URIs if the SQLITE_OPEN_URI flag is set when the
 ** only interpreted as URIs if the SQLITE_OPEN_URI flag is set when the
-** database connection is opened. By default, URI handling is globally
+** database connection is opened. ^(By default, URI handling is globally
 ** disabled. The default value may be changed by compiling with the
 ** disabled. The default value may be changed by compiling with the
-** [SQLITE_USE_URI] symbol defined.
+** [SQLITE_USE_URI] symbol defined.)^
 **
 **
 ** [[SQLITE_CONFIG_COVERING_INDEX_SCAN]] <dt>SQLITE_CONFIG_COVERING_INDEX_SCAN
 ** [[SQLITE_CONFIG_COVERING_INDEX_SCAN]] <dt>SQLITE_CONFIG_COVERING_INDEX_SCAN
-** <dd> This option takes a single integer argument which is interpreted as
+** <dd>^This option takes a single integer argument which is interpreted as
 ** a boolean in order to enable or disable the use of covering indices for
 ** a boolean in order to enable or disable the use of covering indices for
-** full table scans in the query optimizer.  The default setting is determined
+** full table scans in the query optimizer.  ^The default setting is determined
 ** by the [SQLITE_ALLOW_COVERING_INDEX_SCAN] compile-time option, or is "on"
 ** by the [SQLITE_ALLOW_COVERING_INDEX_SCAN] compile-time option, or is "on"
 ** if that compile-time option is omitted.
 ** if that compile-time option is omitted.
 ** The ability to disable the use of covering indices for full table scans
 ** The ability to disable the use of covering indices for full table scans
 ** is because some incorrectly coded legacy applications might malfunction
 ** is because some incorrectly coded legacy applications might malfunction
-** malfunction when the optimization is enabled.  Providing the ability to
+** when the optimization is enabled.  Providing the ability to
 ** disable the optimization allows the older, buggy application code to work
 ** disable the optimization allows the older, buggy application code to work
 ** without change even with newer versions of SQLite.
 ** without change even with newer versions of SQLite.
 **
 **
@@ -1661,16 +1666,16 @@ struct sqlite3_mem_methods {
 **
 **
 ** [[SQLITE_CONFIG_MMAP_SIZE]]
 ** [[SQLITE_CONFIG_MMAP_SIZE]]
 ** <dt>SQLITE_CONFIG_MMAP_SIZE
 ** <dt>SQLITE_CONFIG_MMAP_SIZE
-** <dd>SQLITE_CONFIG_MMAP_SIZE takes two 64-bit integer (sqlite3_int64) values
+** <dd>^SQLITE_CONFIG_MMAP_SIZE takes two 64-bit integer (sqlite3_int64) values
 ** that are the default mmap size limit (the default setting for
 ** that are the default mmap size limit (the default setting for
 ** [PRAGMA mmap_size]) and the maximum allowed mmap size limit.
 ** [PRAGMA mmap_size]) and the maximum allowed mmap size limit.
-** The default setting can be overridden by each database connection using
+** ^The default setting can be overridden by each database connection using
 ** either the [PRAGMA mmap_size] command, or by using the
 ** either the [PRAGMA mmap_size] command, or by using the
-** [SQLITE_FCNTL_MMAP_SIZE] file control.  The maximum allowed mmap size
+** [SQLITE_FCNTL_MMAP_SIZE] file control.  ^(The maximum allowed mmap size
 ** cannot be changed at run-time.  Nor may the maximum allowed mmap size
 ** cannot be changed at run-time.  Nor may the maximum allowed mmap size
 ** exceed the compile-time maximum mmap size set by the
 ** exceed the compile-time maximum mmap size set by the
-** [SQLITE_MAX_MMAP_SIZE] compile-time option.  
-** If either argument to this option is negative, then that argument is
+** [SQLITE_MAX_MMAP_SIZE] compile-time option.)^
+** ^If either argument to this option is negative, then that argument is
 ** changed to its compile-time default.
 ** changed to its compile-time default.
 ** </dl>
 ** </dl>
 */
 */
@@ -2557,9 +2562,10 @@ SQLITE_API SQLITE_EXPERIMENTAL void *sqlite3_profile(sqlite3*,
 ** interface is to keep a GUI updated during a large query.
 ** interface is to keep a GUI updated during a large query.
 **
 **
 ** ^The parameter P is passed through as the only parameter to the 
 ** ^The parameter P is passed through as the only parameter to the 
-** callback function X.  ^The parameter N is the number of 
+** callback function X.  ^The parameter N is the approximate number of 
 ** [virtual machine instructions] that are evaluated between successive
 ** [virtual machine instructions] that are evaluated between successive
-** invocations of the callback X.
+** invocations of the callback X.  ^If N is less than one then the progress
+** handler is disabled.
 **
 **
 ** ^Only a single progress handler may be defined at one time per
 ** ^Only a single progress handler may be defined at one time per
 ** [database connection]; setting a new progress handler cancels the
 ** [database connection]; setting a new progress handler cancels the
@@ -4179,41 +4185,49 @@ SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context*);
 /*
 /*
 ** CAPI3REF: Function Auxiliary Data
 ** CAPI3REF: Function Auxiliary Data
 **
 **
-** The following two functions may be used by scalar SQL functions to
+** These functions may be used by (non-aggregate) SQL functions to
 ** associate metadata with argument values. If the same value is passed to
 ** associate metadata with argument values. If the same value is passed to
 ** multiple invocations of the same SQL function during query execution, under
 ** multiple invocations of the same SQL function during query execution, under
-** some circumstances the associated metadata may be preserved. This may
-** be used, for example, to add a regular-expression matching scalar
-** function. The compiled version of the regular expression is stored as
-** metadata associated with the SQL value passed as the regular expression
-** pattern.  The compiled regular expression can be reused on multiple
-** invocations of the same function so that the original pattern string
-** does not need to be recompiled on each invocation.
+** some circumstances the associated metadata may be preserved.  An example
+** of where this might be useful is in a regular-expression matching
+** function. The compiled version of the regular expression can be stored as
+** metadata associated with the pattern string.  
+** Then as long as the pattern string remains the same,
+** the compiled regular expression can be reused on multiple
+** invocations of the same function.
 **
 **
 ** ^The sqlite3_get_auxdata() interface returns a pointer to the metadata
 ** ^The sqlite3_get_auxdata() interface returns a pointer to the metadata
 ** associated by the sqlite3_set_auxdata() function with the Nth argument
 ** associated by the sqlite3_set_auxdata() function with the Nth argument
-** value to the application-defined function. ^If no metadata has been ever
-** been set for the Nth argument of the function, or if the corresponding
-** function parameter has changed since the meta-data was set,
-** then sqlite3_get_auxdata() returns a NULL pointer.
-**
-** ^The sqlite3_set_auxdata() interface saves the metadata
-** pointed to by its 3rd parameter as the metadata for the N-th
-** argument of the application-defined function.  Subsequent
-** calls to sqlite3_get_auxdata() might return this data, if it has
-** not been destroyed.
-** ^If it is not NULL, SQLite will invoke the destructor
-** function given by the 4th parameter to sqlite3_set_auxdata() on
-** the metadata when the corresponding function parameter changes
-** or when the SQL statement completes, whichever comes first.
-**
-** SQLite is free to call the destructor and drop metadata on any
-** parameter of any function at any time.  ^The only guarantee is that
-** the destructor will be called before the metadata is dropped.
+** value to the application-defined function. ^If there is no metadata
+** associated with the function argument, this sqlite3_get_auxdata() interface
+** returns a NULL pointer.
+**
+** ^The sqlite3_set_auxdata(C,N,P,X) interface saves P as metadata for the N-th
+** argument of the application-defined function.  ^Subsequent
+** calls to sqlite3_get_auxdata(C,N) return P from the most recent
+** sqlite3_set_auxdata(C,N,P,X) call if the metadata is still valid or
+** NULL if the metadata has been discarded.
+** ^After each call to sqlite3_set_auxdata(C,N,P,X) where X is not NULL,
+** SQLite will invoke the destructor function X with parameter P exactly
+** once, when the metadata is discarded.
+** SQLite is free to discard the metadata at any time, including: <ul>
+** <li> when the corresponding function parameter changes, or
+** <li> when [sqlite3_reset()] or [sqlite3_finalize()] is called for the
+**      SQL statement, or
+** <li> when sqlite3_set_auxdata() is invoked again on the same parameter, or
+** <li> during the original sqlite3_set_auxdata() call when a memory 
+**      allocation error occurs. </ul>)^
+**
+** Note the last bullet in particular.  The destructor X in 
+** sqlite3_set_auxdata(C,N,P,X) might be called immediately, before the
+** sqlite3_set_auxdata() interface even returns.  Hence sqlite3_set_auxdata()
+** should be called near the end of the function implementation and the
+** function implementation should not make any use of P after
+** sqlite3_set_auxdata() has been called.
 **
 **
 ** ^(In practice, metadata is preserved between function calls for
 ** ^(In practice, metadata is preserved between function calls for
-** expressions that are constant at compile time. This includes literal
-** values and [parameters].)^
+** function parameters that are compile-time constants, including literal
+** values and [parameters] and expressions composed from the same.)^
 **
 **
 ** These routines must be called from the same thread in which
 ** These routines must be called from the same thread in which
 ** the SQL function is running.
 ** the SQL function is running.
@@ -4518,6 +4532,11 @@ SQLITE_API int sqlite3_key(
   sqlite3 *db,                   /* Database to be rekeyed */
   sqlite3 *db,                   /* Database to be rekeyed */
   const void *pKey, int nKey     /* The key */
   const void *pKey, int nKey     /* The key */
 );
 );
+SQLITE_API int sqlite3_key_v2(
+  sqlite3 *db,                   /* Database to be rekeyed */
+  const char *zDbName,           /* Name of the database */
+  const void *pKey, int nKey     /* The key */
+);
 
 
 /*
 /*
 ** Change the key on an open database.  If the current database is not
 ** Change the key on an open database.  If the current database is not
@@ -4531,6 +4550,11 @@ SQLITE_API int sqlite3_rekey(
   sqlite3 *db,                   /* Database to be rekeyed */
   sqlite3 *db,                   /* Database to be rekeyed */
   const void *pKey, int nKey     /* The new key */
   const void *pKey, int nKey     /* The new key */
 );
 );
+SQLITE_API int sqlite3_rekey_v2(
+  sqlite3 *db,                   /* Database to be rekeyed */
+  const char *zDbName,           /* Name of the database */
+  const void *pKey, int nKey     /* The new key */
+);
 
 
 /*
 /*
 ** Specify the activation key for a SEE database.  Unless 
 ** Specify the activation key for a SEE database.  Unless 
@@ -5116,11 +5140,24 @@ SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff);
 ** on the list of automatic extensions is a harmless no-op. ^No entry point
 ** on the list of automatic extensions is a harmless no-op. ^No entry point
 ** will be called more than once for each database connection that is opened.
 ** will be called more than once for each database connection that is opened.
 **
 **
-** See also: [sqlite3_reset_auto_extension()].
+** See also: [sqlite3_reset_auto_extension()]
+** and [sqlite3_cancel_auto_extension()]
 */
 */
 SQLITE_API int sqlite3_auto_extension(void (*xEntryPoint)(void));
 SQLITE_API int sqlite3_auto_extension(void (*xEntryPoint)(void));
 
 
 /*
 /*
+** CAPI3REF: Cancel Automatic Extension Loading
+**
+** ^The [sqlite3_cancel_auto_extension(X)] interface unregisters the
+** initialization routine X that was registered using a prior call to
+** [sqlite3_auto_extension(X)].  ^The [sqlite3_cancel_auto_extension(X)]
+** routine returns 1 if initialization routine X was successfully 
+** unregistered and it returns 0 if X was not on the list of initialization
+** routines.
+*/
+SQLITE_API int sqlite3_cancel_auto_extension(void (*xEntryPoint)(void));
+
+/*
 ** CAPI3REF: Reset Automatic Extension Loading
 ** CAPI3REF: Reset Automatic Extension Loading
 **
 **
 ** ^This interface disables all automatic extensions previously
 ** ^This interface disables all automatic extensions previously
@@ -6232,6 +6269,12 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
 ** on subsequent SQLITE_DBSTATUS_CACHE_WRITE requests is undefined.)^ ^The
 ** on subsequent SQLITE_DBSTATUS_CACHE_WRITE requests is undefined.)^ ^The
 ** highwater mark associated with SQLITE_DBSTATUS_CACHE_WRITE is always 0.
 ** highwater mark associated with SQLITE_DBSTATUS_CACHE_WRITE is always 0.
 ** </dd>
 ** </dd>
+**
+** [[SQLITE_DBSTATUS_DEFERRED_FKS]] ^(<dt>SQLITE_DBSTATUS_DEFERRED_FKS</dt>
+** <dd>This parameter returns zero for the current value if and only if
+** all foreign key constraints (deferred or immediate) have been
+** resolved.)^  ^The highwater mark is always 0.
+** </dd>
 ** </dl>
 ** </dl>
 */
 */
 #define SQLITE_DBSTATUS_LOOKASIDE_USED       0
 #define SQLITE_DBSTATUS_LOOKASIDE_USED       0
@@ -6244,7 +6287,8 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
 #define SQLITE_DBSTATUS_CACHE_HIT            7
 #define SQLITE_DBSTATUS_CACHE_HIT            7
 #define SQLITE_DBSTATUS_CACHE_MISS           8
 #define SQLITE_DBSTATUS_CACHE_MISS           8
 #define SQLITE_DBSTATUS_CACHE_WRITE          9
 #define SQLITE_DBSTATUS_CACHE_WRITE          9
-#define SQLITE_DBSTATUS_MAX                  9   /* Largest defined DBSTATUS */
+#define SQLITE_DBSTATUS_DEFERRED_FKS        10
+#define SQLITE_DBSTATUS_MAX                 10   /* Largest defined DBSTATUS */
 
 
 
 
 /*
 /*
@@ -6298,11 +6342,21 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg);
 ** A non-zero value in this counter may indicate an opportunity to
 ** A non-zero value in this counter may indicate an opportunity to
 ** improvement performance by adding permanent indices that do not
 ** improvement performance by adding permanent indices that do not
 ** need to be reinitialized each time the statement is run.</dd>
 ** need to be reinitialized each time the statement is run.</dd>
+**
+** [[SQLITE_STMTSTATUS_VM_STEP]] <dt>SQLITE_STMTSTATUS_VM_STEP</dt>
+** <dd>^This is the number of virtual machine operations executed
+** by the prepared statement if that number is less than or equal
+** to 2147483647.  The number of virtual machine operations can be 
+** used as a proxy for the total work done by the prepared statement.
+** If the number of virtual machine operations exceeds 2147483647
+** then the value returned by this statement status code is undefined.
+** </dd>
 ** </dl>
 ** </dl>
 */
 */
 #define SQLITE_STMTSTATUS_FULLSCAN_STEP     1
 #define SQLITE_STMTSTATUS_FULLSCAN_STEP     1
 #define SQLITE_STMTSTATUS_SORT              2
 #define SQLITE_STMTSTATUS_SORT              2
 #define SQLITE_STMTSTATUS_AUTOINDEX         3
 #define SQLITE_STMTSTATUS_AUTOINDEX         3
+#define SQLITE_STMTSTATUS_VM_STEP           4
 
 
 /*
 /*
 ** CAPI3REF: Custom Page Cache Object
 ** CAPI3REF: Custom Page Cache Object
@@ -7181,7 +7235,7 @@ SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *);
 #ifdef __cplusplus
 #ifdef __cplusplus
 }  /* End of the 'extern "C"' block */
 }  /* End of the 'extern "C"' block */
 #endif
 #endif
-#endif
+#endif /* _SQLITE3_H_ */
 
 
 /*
 /*
 ** 2010 August 30
 ** 2010 August 30

+ 1 - 3
test/ajax/echo.lp

@@ -1,8 +1,6 @@
 <?
 <?
-function print(txt) mg.write(txt .. "\r\n") end
-mg.write("HTTP/1.1 200 OK\r\n")
 n = string.match(mg.request_info.uri, "^(.*)%.lp$")
 n = string.match(mg.request_info.uri, "^(.*)%.lp$")
 n = string.gsub(n, [[/]], [[\]])
 n = string.gsub(n, [[/]], [[\]])
-n = mg.document_root .. n .. ".cgi"
+n = mg.document_root .. n .. ".lua"
 dofile(n)
 dofile(n)
 ?>
 ?>

+ 35 - 0
test/ajax/echo.lua

@@ -0,0 +1,35 @@
+resp = "{";
+
+method = mg.request_info.request_method
+uri = mg.request_info.uri
+query = mg.request_info.query_string
+datalen = nil -- TODO: "CONTENT_LENGTH" !
+
+if method then
+  resp = resp .. '"method" : "' .. method .. '", ';
+end
+if uri then
+  resp = resp .. '"uri" : "' .. uri .. '", ';
+end
+if query then
+  resp = resp .. '"query" : "' .. query .. '", ';
+end
+if datalen then
+  resp = resp .. '"datalen" : "' .. datalen .. '", ';
+end
+
+resp = resp .. '"time" : "' .. os.date() .. '" ';
+
+resp = resp .. "}";
+
+
+
+mg.write("HTTP/1.1 200 OK\n")
+mg.write("Connection: close\n")
+mg.write("Content-Type: text/html\n")
+mg.write("Cache-Control: no-cache\n")
+--mg.write("Content-Length: " .. resp:len() .. "\n")
+mg.write("\n")
+
+mg.write(resp)
+

+ 7 - 1
test/ajax/test.html

@@ -19,6 +19,12 @@
     var autoTest = false;
     var autoTest = false;
     var testType = "cgi";
     var testType = "cgi";
 
 
+    function NextTestType() {
+      if (testType == "cgi") testType = "lp";
+      else if (testType == "lp") testType = "lua";
+      else testType = "cgi";
+    }
+
     function runTest(method, isAsync) {
     function runTest(method, isAsync) {
 
 
       ++pushCount;
       ++pushCount;
@@ -120,7 +126,7 @@
             <input id="testButton5" type="button" onclick="autoTest=!autoTest; javascript:runAutoTest()" value="automatic test"></input>
             <input id="testButton5" type="button" onclick="autoTest=!autoTest; javascript:runAutoTest()" value="automatic test"></input>
           </td>        
           </td>        
           <td>
           <td>
-            <input id="testButton6" type="button" onclick="testType = (testType=='cgi') ? 'lp' : 'cgi'; this.value=testType" value='cgi'></input>
+            <input id="testButton6" type="button" onclick="NextTestType(); this.value=testType" value='cgi'></input>
           </td>        
           </td>        
         </tr>
         </tr>
         
         

+ 10 - 10
test/page2.lp

@@ -12,30 +12,30 @@ The following features are available:
   -- function in one Lua tag should still be available in the next one
   -- function in one Lua tag should still be available in the next one
   function test(tab, name)
   function test(tab, name)
     if tab then
     if tab then
-      mg.write("<li>" .. name .. "</li>")
+      mg.write("<li>" .. name .. "</li>\n")
     end
     end
   end
   end
   function recurse(tab)
   function recurse(tab)
-    mg.write("<ul>")
+    mg.write("<ul>\n")
     for k,v in pairs(tab) do      
     for k,v in pairs(tab) do      
       if type(v) == "table" then
       if type(v) == "table" then
-        mg.write("<li>" .. tostring(k) .. ":</li>")
+        mg.write("<li>" .. tostring(k) .. ":</li>\n")
         recurse(v)
         recurse(v)
       else
       else
-        mg.write("<li>" .. tostring(k) .. " = " .. tostring(v) .. "</li>")        
+        mg.write("<li>" .. tostring(k) .. " = " .. tostring(v) .. "</li>\n")        
       end
       end
     end
     end
-    mg.write("</ul>")
+    mg.write("</ul>\n")
   end
   end
 ?>
 ?>
 <?
 <?
-  mg.write("<li>" .. _VERSION .. " with the following standard libraries</li>")
+  mg.write("<li>" .. _VERSION .. " with the following standard libraries</li>\n")
   mg.write("<ul>")
   mg.write("<ul>")
   libs = {"string", "math", "table", "io", "os", "bit32", "package", "coroutine", "debug"};
   libs = {"string", "math", "table", "io", "os", "bit32", "package", "coroutine", "debug"};
   for _,n in ipairs(libs) do
   for _,n in ipairs(libs) do
     test(_G[n], n);
     test(_G[n], n);
   end
   end
-  mg.write("</ul>")
+  mg.write("</ul>\n")
   test(sqlite3, "sqlite3 binding")
   test(sqlite3, "sqlite3 binding")
   test(lfs,"lua file system")
   test(lfs,"lua file system")
   
   
@@ -52,13 +52,13 @@ The following features are available:
 
 
   if lfs then    
   if lfs then    
     mg.write("Files in " .. lfs.currentdir())
     mg.write("Files in " .. lfs.currentdir())
-    mg.write("<ul>")
+    mg.write("\n<ul>\n")
     for f in lfs.dir(".") do
     for f in lfs.dir(".") do
-      mg.write("<li>" .. f .. "</li>")
+      mg.write("<li>" .. f .. "</li>\n")
       local at = lfs.attributes(f);
       local at = lfs.attributes(f);
       recurse(at)
       recurse(at)
     end
     end
-    mg.write("</ul>")
+    mg.write("</ul>\n")
   end
   end
 ?>
 ?>
 </p>
 </p>

+ 64 - 0
test/page2.lua

@@ -0,0 +1,64 @@
+mg.write("HTTP/1.0 200 OK\r\n")
+mg.write("Content-Type: text/html\r\n")
+mg.write("\r\n")
+mg.write([[<html><body>
+
+<p>This is another example of a Lua server page, served by
+<a href="http://code.google.com/p/civetweb">Civetweb web server</a>.
+</p><p>
+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")
+  end
+
+  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
+    test(_G[n], n);
+  end
+  mg.write("</ul>\n")
+  test(sqlite3, "sqlite3 binding")
+  test(lfs,"lua file system")
+  
+  libname = "mg"
+  test(_G[libname], libname .. " library")
+  recurse(_G[libname])
+
+  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>
+</body></html>
+]])

+ 62 - 0
test/websocket.lua

@@ -0,0 +1,62 @@
+-- Open database
+local db = sqlite3.open('r:\\websockLog.db')
+
+if db then
+  db:busy_timeout(200);
+  -- 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
+    );
+  ]])
+end
+
+
+local function logDB(method)
+  -- Add entry about this request
+  local r;
+  repeat
+    r = db:exec([[INSERT INTO requests VALUES(NULL, datetime("now"), "]] .. method .. [[", "]] .. mg.request_info.uri .. [[", "]] .. mg.request_info.remote_port .. [[");]]);
+  until r~=5;
+end
+
+
+-- Callback for "Websocket ready"
+function ready()
+  logDB("WEBSOCKET READY")
+  mg.write("text", "Websocket ready")
+end
+
+-- Callback for "Websocket received data"
+function data(bits, content)
+    logDB(string.format("WEBSOCKET DATA (%x)", bits))
+    mg.write("text", os.date())
+    return true;
+end
+
+-- Callback for "Websocket is closing"
+function close()
+  logDB("WEBSOCKET CLOSE")
+  -- Close database
+  db:close()
+end
+
+
+-- Websocket with coroutines
+logDB("WEBSOCKET PREPARE")
+
+coroutine.yield(true); -- first yield returns (true) or (false) to accept or reject the connection
+ready()
+repeat
+    local cont, bits, content = coroutine.yield(true, 1.0)
+    if bits and content then
+        data(bits, content)
+    end
+until not cont;
+
+mg.write("text", "end")
+close()

+ 67 - 0
test/websocket.xhtml

@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <meta charset="UTF-8"></meta>
+  <title>Websocket test</title>
+  <style type="text/css" media="screen">
+    body { background:#eee; margin:0 }
+    .main {
+      display:block; border:1px solid #ccc; position:absolute;
+      top:5%; left:5%; width:90%; height:90%; background:#fff;
+    }
+  </style>
+</head>
+<body>
+
+  <script type="text/javascript"><![CDATA[
+
+    var connection;
+    var websock_text_field;
+
+    function webSockKeepAlive() {
+      if (keepAlive) {
+        connection.send('client still alive');
+        setTimeout("webSockKeepAlive()", 10000);
+      }
+    }
+
+    function load() {
+      connection = new WebSocket("ws://" + window.location.host + "/websocket.lua");
+      websock_text_field = document.getElementById('websock_text_field');
+
+      connection.onopen = function () {
+        keepAlive = true;
+        webSockKeepAlive();
+      };
+
+      // Log errors
+      connection.onerror = function (error) {
+        keepAlive = false;
+        alert("WebSocket error");
+        connection.close();
+      };
+
+      // Log messages from the server
+      connection.onmessage = function (e) {        
+        websock_text_field.textContent = e.data;
+      };
+    }
+
+  ]]></script>
+
+<svg class="main"
+  xmlns="http://www.w3.org/2000/svg"
+  xmlns:svg="http://www.w3.org/2000/svg"
+  version="1.1"
+  xmlns:xlink="http://www.w3.org/1999/xlink"
+  viewBox="0 0 1600 1200" preserveAspectRatio="xMinYMin meet"
+  onload="load()"
+  >
+
+  <circle id="line_a" cx="800" cy="600" r="500" style="stroke:rgb(255,0,0); stroke-width:5; fill:rgb(200,200,200)"/>
+  <text id="websock_text_field" x="800" y="600" text-anchor="middle" font-size="50px" fill="red">No websocket connection yet</text>
+
+</svg>
+
+</body>
+</html>

Some files were not shown because too many files changed in this diff