diff --git a/Makefile b/Makefile index 6731d484..c637b87b 100644 --- a/Makefile +++ b/Makefile @@ -22,8 +22,11 @@ all: ### UNIX build: linux, bsd, mac, rtems ########################################################################## +# Add $(LUA_FLAGS) to CFLAGS below if you want to build with Lua +LUA = lua-5.2.1/src +LUA_FLAGS = -DUSE_LUA -I$(LUA) -L$(LUA) -llua -lm + GCC_WARNS = -W -Wall -pedantic -# -Wno-missing-field-initializers -Wno-unused-parameter -Wno-format-zero-length -Wno-missing-braces CFLAGS = -std=c99 -O2 $(GCC_WARNS) $(COPT) MAC_SHARED = -flat_namespace -bundle -undefined suppress LINFLAGS = -ldl -pthread $(CFLAGS) @@ -64,14 +67,26 @@ CYA = e:/cyassl-2.0.0rc2 #DBG = /Zi /DDEBUG /Od DBG = /DNDEBUG /O1 CL = $(MSVC)/bin/cl /MD /TC /nologo $(DBG) /Gz /W3 /DNO_SSL_DL \ - /I$(MSVC)/include + /I$(MSVC)/include /DUSE_LUA /I$(LUA) GUILIB= user32.lib shell32.lib LINK = /link /incremental:no /libpath:$(MSVC)/lib /machine:IX86 \ - /subsystem:windows ws2_32.lib advapi32.lib cyassl.lib + /subsystem:windows ws2_32.lib advapi32.lib cyassl.lib lua.lib CYAFL = /c /I $(CYA)/include -I $(CYA)/include/openssl /I$(MSVC)/INCLUDE \ /I $(CYA)/ctaocrypt/include /D _LIB /D OPENSSL_EXTRA -CYASRC= \ +LUA_SOURCES = $(LUA)/lapi.c $(LUA)/lcode.c $(LUA)/lctype.c \ + $(LUA)/ldebug.c $(LUA)/ldo.c $(LUA)/ldump.c \ + $(LUA)/lfunc.c $(LUA)/lgc.c $(LUA)/llex.c \ + $(LUA)/lmem.c $(LUA)/lobject.c $(LUA)/lopcodes.c \ + $(LUA)/lparser.c $(LUA)/lstate.c $(LUA)/lstring.c \ + $(LUA)/ltable.c $(LUA)/ltm.c $(LUA)/lundump.c \ + $(LUA)/lvm.c $(LUA)/lzio.c $(LUA)/lauxlib.c \ + $(LUA)/lbaselib.c $(LUA)/lbitlib.c $(LUA)/lcorolib.c \ + $(LUA)/ldblib.c $(LUA)/liolib.c $(LUA)/lmathlib.c \ + $(LUA)/loslib.c $(LUA)/lstrlib.c $(LUA)/ltablib.c \ + $(LUA)/loadlib.c $(LUA)/linit.c + +CYA_SOURCES = \ $(CYA)/src/cyassl_int.c \ $(CYA)/src/cyassl_io.c \ $(CYA)/src/keys.c \ @@ -104,10 +119,14 @@ CYASRC= \ $(CYA)/ctaocrypt/src/tfm.c cyassl.lib: - $(CL) $(CYASRC) $(CYAFL) $(DEF) - $(MSVC)/bin/lib *.obj /out:$@ + $(CL) /Fo$(CYA)/ $(CYA_SOURCES) $(CYAFL) $(DEF) + $(MSVC)/bin/lib $(CYA)/*.obj /out:$@ -windows: cyassl.lib +lua.lib: + $(CL) /c /Fo$(LUA)/ $(LUA_SOURCES) + $(MSVC)/bin/lib $(LUA_SOURCES:%.c=%.obj) /out:$@ + +windows: cyassl.lib lua.lib $(MSVC)/bin/rc win32\res.rc $(CL) /I win32 main.c mongoose.c /GA $(LINK) win32\res.res \ $(GUILIB) /out:$(PROG).exe @@ -144,4 +163,4 @@ release: clean F=mongoose-`perl -lne '/define\s+MONGOOSE_VERSION\s+"(\S+)"/ and print $$1' mongoose.c`.tgz ; cd .. && tar -czf x mongoose/{LICENSE,Makefile,bindings,examples,test,win32,mongoose.c,mongoose.h,mongoose.1,main.c} && mv x mongoose/$$F clean: - rm -rf *.o *.core $(PROG) *.obj *.so $(PROG).txt *.dSYM *.tgz $(PROG).exe *.dll *.lib + rm -rf *.o *.core $(PROG) *.obj *.so $(PROG).txt *.dSYM *.tgz $(PROG).exe *.dll *.lib \ No newline at end of file diff --git a/mongoose.c b/mongoose.c index 8ebb82ef..9754d954 100644 --- a/mongoose.c +++ b/mongoose.c @@ -218,6 +218,11 @@ typedef int SOCKET; #include "mongoose.h" +#ifdef USE_LUA +#include +#include +#endif + #define MONGOOSE_VERSION "3.4" #define PASSWORDS_FILE_NAME ".htpasswd" #define CGI_ENVIRONMENT_SIZE 4096 @@ -1530,7 +1535,7 @@ int mg_write(struct mg_connection *conn, const void *buf, size_t len) { conn->last_throttle_bytes += total; while (total < (int64_t) len && conn->ctx->stop_flag == 0) { allowed = conn->throttle > (int64_t) len - total ? - len - total : conn->throttle; + (int64_t) len - total : conn->throttle; if ((n = push(NULL, conn->client.sock, conn->ssl, (const char *) buf, (int64_t) allowed)) != allowed) { break; @@ -3835,6 +3840,152 @@ static uint32_t get_remote_ip(const struct mg_connection *conn) { return ntohl(* (uint32_t *) &conn->client.rsa.sin.sin_addr); } +#ifdef USE_LUA + +#ifdef _WIN32 +static void *mmap(void *addr, int64_t len, int prot, int flags, int fd, + int offset) { + HANDLE fh = (HANDLE) _get_osfhandle(fd); + HANDLE mh = CreateFileMapping(fh, 0, PAGE_READONLY, 0, 0, 0); + void *p = MapViewOfFile(mh, FILE_MAP_READ, 0, 0, (size_t) len); + CloseHandle(fh); + CloseHandle(mh); + return p; +} +#define munmap(x, y) UnmapViewOfFile(x) +#define MAP_FAILED NULL +#define PROT_READ 0 +#else +#include +#endif + +static void lsp(struct mg_connection *conn, const char *p, int64_t len, + lua_State *L) { + int i, j, pos = 0; + + for (i = 0; i < len; i++) { + if (p[i] == '<' && p[i + 1] == '?') { + for (j = i + 1; j < len ; j++) { + if (p[j] == '?' && p[j + 1] == '>') { + mg_write(conn, p + pos, i - pos); + if (luaL_loadbuffer(L, p + (i + 2), j - (i + 2), "") == LUA_OK) { + lua_pcall(L, 0, LUA_MULTRET, 0); + } + pos = j + 2; + i = pos - 1; + break; + } + } + } + } + + if (i > pos) { + mg_write(conn, p + pos, i - pos); + } +} + +static int lsp_mg_print(lua_State *L) { + int i, num_args; + const char *str; + size_t size; + struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1)); + + num_args = lua_gettop(L); + for (i = 1; i <= num_args; i++) { + if (lua_isstring(L, i)) { + str = lua_tolstring(L, i, &size); + mg_write(conn, str, size); + } + } + + return 0; +} + +static int lsp_mg_read(lua_State *L) { + struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1)); + char buf[1024]; + int len = mg_read(conn, buf, sizeof(buf)); + + lua_settop(L, 0); + lua_pushlstring(L, buf, len); + + return 1; +} + +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); +} + +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); +} + +static void prepare_lua_environment(struct mg_connection *conn, lua_State *L) { + const struct mg_request_info *ri = mg_get_request_info(conn); + extern void luaL_openlibs(lua_State *); + int i; + + luaL_openlibs(L); + + // Register "print" function which calls mg_write() + lua_pushlightuserdata(L, conn); + lua_pushcclosure(L, lsp_mg_print, 1); + lua_setglobal(L, "print"); + + // Register mg_read() + lua_pushlightuserdata(L, conn); + lua_pushcclosure(L, lsp_mg_read, 1); + lua_setglobal(L, "read"); + + // Export request_info + lua_newtable(L); + reg_string(L, "request_method", ri->request_method); + reg_string(L, "uri", ri->uri); + reg_string(L, "http_version", ri->http_version); + reg_string(L, "query_string", ri->query_string); + reg_int(L, "remote_ip", ri->remote_ip); + reg_int(L, "remote_port", ri->remote_port); + reg_int(L, "num_headers", ri->num_headers); + lua_pushstring(L, "http_headers"); + lua_newtable(L); + for (i = 0; i < ri->num_headers; i++) { + reg_string(L, ri->http_headers[i].name, ri->http_headers[i].value); + } + lua_rawset(L, -3); + lua_setglobal(L, "request_info"); +} + +static void handle_lsp_request(struct mg_connection *conn, const char *path, + const struct mgstat *st) { + void *p = NULL; + FILE *fp = NULL; + lua_State *L = NULL; + + if ((fp = fopen(path, "r")) == NULL) { + send_http_error(conn, 404, "Not Found", "%s", "File not found"); + } else if ((p = mmap(NULL, st->size, PROT_READ, 0, + fileno(fp), 0)) == MAP_FAILED) { + send_http_error(conn, 500, http_500_error, "%s", "x"); + } else if ((L = luaL_newstate()) == NULL) { + send_http_error(conn, 500, http_500_error, "%s", "y"); + } else { + mg_printf(conn, "%s", "HTTP/1.1 200 OK\r\n" + "Content-Type: text/html\r\nConnection: close\r\n\r\n"); + prepare_lua_environment(conn, L); + lsp(conn, p, st->size, L); + } + + if (L) lua_close(L); + if (p) munmap(p, st->size); + if (fp) fclose(fp); + +} +#endif // USE_LUA + // This is the heart of the Mongoose's logic. // This function is called when the request is read, parsed and validated, // and Mongoose must decide what action to take: serve a file, or @@ -3897,6 +4048,10 @@ static void handle_request(struct mg_connection *conn) { send_http_error(conn, 403, "Directory Listing Denied", "Directory listing denied"); } +#ifdef USE_LUA + } else if (match_prefix("**.lsp$", 7, path) > 0) { + handle_lsp_request(conn, path, &st); +#endif #if !defined(NO_CGI) } else if (match_prefix(conn->ctx->config[CGI_EXTENSIONS], strlen(conn->ctx->config[CGI_EXTENSIONS]), diff --git a/test/page.lsp b/test/page.lsp new file mode 100644 index 00000000..0f7901e1 --- /dev/null +++ b/test/page.lsp @@ -0,0 +1,40 @@ + +

Prime numbers from 0 to 100, calculated by Lua:

+ ' .. i .. ' ') end + end + + ?> + +

Reading POST data from Lua (click submit):

+
+ +
+   POST data: []
+   request method: []
+   IP/port: []
+   URI: []
+   HTTP version []
+   HEADERS:
+   
+     
+ 
+ + + diff --git a/test/unit_test.c b/test/unit_test.c index 62df8ade..47dc0de6 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -31,7 +31,7 @@ static void test_parse_http_request() { ASSERT(strcmp(ri.http_headers[2].name, "baz\r\n\r") == 0); ASSERT(strcmp(ri.http_headers[2].value, "") == 0); - // TODO(lsm): add more tests. + // TODO(lsm): add more tests. } static void test_should_keep_alive(void) { @@ -210,18 +210,18 @@ static void test_base64_encode(void) { static void test_mg_get_var(void) { static const char *post[] = {"a=1&&b=2&d&=&c=3%20&e=", NULL}; char buf[20]; - + ASSERT(mg_get_var(post[0], strlen(post[0]), "a", buf, sizeof(buf)) == 1); ASSERT(buf[0] == '1' && buf[1] == '\0'); ASSERT(mg_get_var(post[0], strlen(post[0]), "b", buf, sizeof(buf)) == 1); ASSERT(buf[0] == '2' && buf[1] == '\0'); - ASSERT(mg_get_var(post[0], strlen(post[0]), "c", buf, sizeof(buf)) == 2); + ASSERT(mg_get_var(post[0], strlen(post[0]), "c", buf, sizeof(buf)) == 2); ASSERT(buf[0] == '3' && buf[1] == ' ' && buf[2] == '\0'); ASSERT(mg_get_var(post[0], strlen(post[0]), "e", buf, sizeof(buf)) == 0); ASSERT(buf[0] == '\0'); - ASSERT(mg_get_var(post[0], strlen(post[0]), "d", buf, sizeof(buf)) == -1); - ASSERT(mg_get_var(post[0], strlen(post[0]), "c", buf, 2) == -1); + ASSERT(mg_get_var(post[0], strlen(post[0]), "d", buf, sizeof(buf)) == -1); + ASSERT(mg_get_var(post[0], strlen(post[0]), "c", buf, 2) == -1); ASSERT(mg_get_var(post[0], strlen(post[0]), "x", NULL, 10) == -2); ASSERT(mg_get_var(post[0], strlen(post[0]), "x", buf, 0) == -2); @@ -255,6 +255,54 @@ static void test_next_option(void) { } } +#ifdef USE_LUA +static void check_lua_expr(lua_State *L, const char *expr, const char *value) { + const char *v, *var_name = "myVar"; + char buf[100]; + + snprintf(buf, sizeof(buf), "%s = %s", var_name, expr); + luaL_dostring(L, buf); + lua_getglobal(L, var_name); + v = lua_tostring(L, -1); + printf("%s: %s: [%s] [%s]\n", __func__, expr, v == NULL ? "null" : v, value); + ASSERT((value == NULL && v == NULL) || + (value != NULL && v != NULL && !strcmp(value, v))); +} + +static void test_lua(void) { + static struct mg_connection conn; + static struct mg_context ctx; + + char http_request[] = "POST /foo/bar HTTP/1.1\r\n" + "Content-Length: 12\r\n" + "Connection: close\r\n\r\nhello world!"; + const char *page = ""; + lua_State *L = luaL_newstate(); + + conn.ctx = &ctx; + conn.buf = http_request; + conn.buf_size = conn.data_len = strlen(http_request); + conn.request_len = parse_http_request(conn.buf, conn.data_len, + &conn.request_info); + conn.content_len = conn.data_len - conn.request_len; + + prepare_lua_environment(&conn, L); + ASSERT(lua_gettop(L) == 0); + + check_lua_expr(L, "'hi'", "hi"); + check_lua_expr(L, "request_info.request_method", "POST"); + check_lua_expr(L, "request_info.uri", "/foo/bar"); + check_lua_expr(L, "request_info.num_headers", "2"); + check_lua_expr(L, "request_info.remote_ip", "0"); + check_lua_expr(L, "request_info.http_headers['Content-Length']", "12"); + check_lua_expr(L, "request_info.http_headers['Connection']", "close"); + luaL_dostring(L, "post = read()"); + check_lua_expr(L, "# post", "12"); + check_lua_expr(L, "post", "hello world!"); + lua_close(L); +} +#endif + int main(void) { test_base64_encode(); test_match_prefix(); @@ -265,5 +313,8 @@ int main(void) { test_mg_get_var(); test_set_throttle(); test_next_option(); +#ifdef USE_LUA + test_lua(); +#endif return 0; }