diff options
author | nbd <nbd@3c298f89-4303-0410-b956-a3cf2f4a3e73> | 2012-10-10 12:32:29 +0000 |
---|---|---|
committer | nbd <nbd@3c298f89-4303-0410-b956-a3cf2f4a3e73> | 2012-10-10 12:32:29 +0000 |
commit | 9c8997d54dc9df184bfcedeabf0b3c85cf5e6753 (patch) | |
tree | 46b83031a0da1b4458317413c00d13c252c72afa /package/network/services/uhttpd/src/uhttpd-lua.c | |
parent | eecf5b17520f6b3b6ffb45ac7dca298d93b27501 (diff) |
packages: sort network related packages into package/network/
git-svn-id: svn://svn.openwrt.org/openwrt/trunk@33688 3c298f89-4303-0410-b956-a3cf2f4a3e73
Diffstat (limited to 'package/network/services/uhttpd/src/uhttpd-lua.c')
-rw-r--r-- | package/network/services/uhttpd/src/uhttpd-lua.c | 579 |
1 files changed, 579 insertions, 0 deletions
diff --git a/package/network/services/uhttpd/src/uhttpd-lua.c b/package/network/services/uhttpd/src/uhttpd-lua.c new file mode 100644 index 000000000..e11393739 --- /dev/null +++ b/package/network/services/uhttpd/src/uhttpd-lua.c @@ -0,0 +1,579 @@ +/* + * uhttpd - Tiny single-threaded httpd - Lua handler + * + * Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "uhttpd.h" +#include "uhttpd-utils.h" +#include "uhttpd-lua.h" + + +static int uh_lua_recv(lua_State *L) +{ + size_t length; + + char buffer[UH_LIMIT_MSGHEAD]; + + int to = 1; + int fd = fileno(stdin); + int rlen = 0; + + length = luaL_checknumber(L, 1); + + if ((length > 0) && (length <= sizeof(buffer))) + { + /* receive data */ + rlen = uh_raw_recv(fd, buffer, length, to); + + /* data read */ + if (rlen > 0) + { + lua_pushnumber(L, rlen); + lua_pushlstring(L, buffer, rlen); + return 2; + } + + /* eof */ + else if (rlen == 0) + { + lua_pushnumber(L, 0); + return 1; + } + + /* no, timeout and actually no data */ + else + { + lua_pushnumber(L, -1); + return 1; + } + } + + /* parameter error */ + lua_pushnumber(L, -2); + return 1; +} + +static int uh_lua_send_common(lua_State *L, bool chunked) +{ + size_t length; + + char chunk[16]; + const char *buffer; + + int rv; + int to = 1; + int fd = fileno(stdout); + int slen = 0; + + buffer = luaL_checklstring(L, 1, &length); + + if (chunked) + { + if (length > 0) + { + snprintf(chunk, sizeof(chunk), "%X\r\n", length); + + ensure_out(rv = uh_raw_send(fd, chunk, strlen(chunk), to)); + slen += rv; + + ensure_out(rv = uh_raw_send(fd, buffer, length, to)); + slen += rv; + + ensure_out(rv = uh_raw_send(fd, "\r\n", 2, to)); + slen += rv; + } + else + { + slen = uh_raw_send(fd, "0\r\n\r\n", 5, to); + } + } + else + { + slen = uh_raw_send(fd, buffer, length, to); + } + +out: + lua_pushnumber(L, slen); + return 1; +} + +static int uh_lua_send(lua_State *L) +{ + return uh_lua_send_common(L, false); +} + +static int uh_lua_sendc(lua_State *L) +{ + return uh_lua_send_common(L, true); +} + +static int uh_lua_str2str(lua_State *L, int (*xlate_func) (char *, int, const char *, int)) +{ + size_t inlen; + int outlen; + const char *inbuf; + char outbuf[UH_LIMIT_MSGHEAD]; + + inbuf = luaL_checklstring(L, 1, &inlen); + outlen = (* xlate_func)(outbuf, sizeof(outbuf), inbuf, inlen); + if (outlen < 0) + luaL_error(L, "%s on URL-encode codec", + (outlen==-1) ? "buffer overflow" : "malformed string"); + + lua_pushlstring(L, outbuf, outlen); + return 1; +} + +static int uh_lua_urldecode(lua_State *L) +{ + return uh_lua_str2str( L, uh_urldecode ); +} + + +static int uh_lua_urlencode(lua_State *L) +{ + return uh_lua_str2str( L, uh_urlencode ); +} + + +lua_State * uh_lua_init(const struct config *conf) +{ + lua_State *L = lua_open(); + const char *err_str = NULL; + + /* Load standard libaries */ + luaL_openlibs(L); + + /* build uhttpd api table */ + lua_newtable(L); + + /* register global send and receive functions */ + lua_pushcfunction(L, uh_lua_recv); + lua_setfield(L, -2, "recv"); + + lua_pushcfunction(L, uh_lua_send); + lua_setfield(L, -2, "send"); + + lua_pushcfunction(L, uh_lua_sendc); + lua_setfield(L, -2, "sendc"); + + lua_pushcfunction(L, uh_lua_urldecode); + lua_setfield(L, -2, "urldecode"); + + lua_pushcfunction(L, uh_lua_urlencode); + lua_setfield(L, -2, "urlencode"); + + /* Pass the document-root to the Lua handler by placing it in + ** uhttpd.docroot. It could alternatively be placed in env.DOCUMENT_ROOT + ** which would more closely resemble the CGI protocol; but would mean that + ** it is not available at the time when the handler-chunk is loaded but + ** rather not until the handler is called, without any code savings. */ + lua_pushstring(L, conf->docroot); + lua_setfield(L, -2, "docroot"); + + /* _G.uhttpd = { ... } */ + lua_setfield(L, LUA_GLOBALSINDEX, "uhttpd"); + + + /* load Lua handler */ + switch (luaL_loadfile(L, conf->lua_handler)) + { + case LUA_ERRSYNTAX: + fprintf(stderr, + "Lua handler contains syntax errors, unable to continue\n"); + exit(1); + + case LUA_ERRMEM: + fprintf(stderr, + "Lua handler ran out of memory, unable to continue\n"); + exit(1); + + case LUA_ERRFILE: + fprintf(stderr, + "Lua cannot open the handler script, unable to continue\n"); + exit(1); + + default: + /* compile Lua handler */ + switch (lua_pcall(L, 0, 0, 0)) + { + case LUA_ERRRUN: + err_str = luaL_checkstring(L, -1); + fprintf(stderr, + "Lua handler had runtime error, " + "unable to continue\n" + "Error: %s\n", err_str); + exit(1); + + case LUA_ERRMEM: + err_str = luaL_checkstring(L, -1); + fprintf(stderr, + "Lua handler ran out of memory, " + "unable to continue\n" + "Error: %s\n", err_str); + exit(1); + + default: + /* test handler function */ + lua_getglobal(L, UH_LUA_CALLBACK); + + if (! lua_isfunction(L, -1)) + { + fprintf(stderr, + "Lua handler provides no "UH_LUA_CALLBACK"(), " + "unable to continue\n"); + exit(1); + } + + lua_pop(L, 1); + break; + } + + break; + } + + return L; +} + +static void uh_lua_shutdown(struct uh_lua_state *state) +{ + free(state); +} + +static bool uh_lua_socket_cb(struct client *cl) +{ + int len; + char buf[UH_LIMIT_MSGHEAD]; + + struct uh_lua_state *state = (struct uh_lua_state *)cl->priv; + + /* there is unread post data waiting */ + while (state->content_length > 0) + { + /* remaining data in http head buffer ... */ + if (cl->httpbuf.len > 0) + { + len = min(state->content_length, cl->httpbuf.len); + + D("Lua: Child(%d) feed %d HTTP buffer bytes\n", cl->proc.pid, len); + + memcpy(buf, cl->httpbuf.ptr, len); + + cl->httpbuf.len -= len; + cl->httpbuf.ptr += len; + } + + /* read it from socket ... */ + else + { + len = uh_tcp_recv(cl, buf, min(state->content_length, sizeof(buf))); + + if ((len < 0) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) + break; + + D("Lua: Child(%d) feed %d/%d TCP socket bytes\n", + cl->proc.pid, len, min(state->content_length, sizeof(buf))); + } + + if (len) + state->content_length -= len; + else + state->content_length = 0; + + /* ... write to Lua process */ + len = uh_raw_send(cl->wpipe.fd, buf, len, + cl->server->conf->script_timeout); + + /* explicit EOF notification for the child */ + if (state->content_length <= 0) + uh_ufd_remove(&cl->wpipe); + } + + /* try to read data from child */ + while ((len = uh_raw_recv(cl->rpipe.fd, buf, sizeof(buf), -1)) > 0) + { + /* pass through buffer to socket */ + D("Lua: Child(%d) relaying %d normal bytes\n", cl->proc.pid, len); + ensure_out(uh_tcp_send(cl, buf, len)); + state->data_sent = true; + } + + /* got EOF or read error from child */ + if ((len == 0) || + ((errno != EAGAIN) && (errno != EWOULDBLOCK) && (len == -1))) + { + D("Lua: Child(%d) presumed dead [%s]\n", + cl->proc.pid, strerror(errno)); + + goto out; + } + + return true; + +out: + if (!state->data_sent) + { + if (cl->timeout.pending) + uh_http_sendhf(cl, 502, "Bad Gateway", + "The Lua process did not produce any response\n"); + else + uh_http_sendhf(cl, 504, "Gateway Timeout", + "The Lua process took too long to produce a " + "response\n"); + } + + uh_lua_shutdown(state); + return false; +} + +bool uh_lua_request(struct client *cl, lua_State *L) +{ + int i; + char *query_string; + const char *prefix = cl->server->conf->lua_prefix; + const char *err_str = NULL; + + int rfd[2] = { 0, 0 }; + int wfd[2] = { 0, 0 }; + + pid_t child; + + struct uh_lua_state *state; + struct http_request *req = &cl->request; + + int content_length = cl->httpbuf.len; + + + /* allocate state */ + if (!(state = malloc(sizeof(*state)))) + { + uh_client_error(cl, 500, "Internal Server Error", "Out of memory"); + return false; + } + + /* spawn pipes for me->child, child->me */ + if ((pipe(rfd) < 0) || (pipe(wfd) < 0)) + { + if (rfd[0] > 0) close(rfd[0]); + if (rfd[1] > 0) close(rfd[1]); + if (wfd[0] > 0) close(wfd[0]); + if (wfd[1] > 0) close(wfd[1]); + + uh_client_error(cl, 500, "Internal Server Error", + "Failed to create pipe: %s", strerror(errno)); + + return false; + } + + + switch ((child = fork())) + { + case -1: + uh_client_error(cl, 500, "Internal Server Error", + "Failed to fork child: %s", strerror(errno)); + + return false; + + case 0: +#ifdef DEBUG + sleep(atoi(getenv("UHTTPD_SLEEP_ON_FORK") ?: "0")); +#endif + + /* do not leak parent epoll descriptor */ + uloop_done(); + + /* close loose pipe ends */ + close(rfd[0]); + close(wfd[1]); + + /* patch stdout and stdin to pipes */ + dup2(rfd[1], 1); + dup2(wfd[0], 0); + + /* avoid leaking our pipe into child-child processes */ + fd_cloexec(rfd[1]); + fd_cloexec(wfd[0]); + + /* put handler callback on stack */ + lua_getglobal(L, UH_LUA_CALLBACK); + + /* build env table */ + lua_newtable(L); + + /* request method */ + lua_pushstring(L, http_methods[req->method]); + lua_setfield(L, -2, "REQUEST_METHOD"); + + /* request url */ + lua_pushstring(L, req->url); + lua_setfield(L, -2, "REQUEST_URI"); + + /* script name */ + lua_pushstring(L, cl->server->conf->lua_prefix); + lua_setfield(L, -2, "SCRIPT_NAME"); + + /* query string, path info */ + if ((query_string = strchr(req->url, '?')) != NULL) + { + lua_pushstring(L, query_string + 1); + lua_setfield(L, -2, "QUERY_STRING"); + + if ((int)(query_string - req->url) > strlen(prefix)) + { + lua_pushlstring(L, + &req->url[strlen(prefix)], + (int)(query_string - req->url) - strlen(prefix) + ); + + lua_setfield(L, -2, "PATH_INFO"); + } + } + else if (strlen(req->url) > strlen(prefix)) + { + lua_pushstring(L, &req->url[strlen(prefix)]); + lua_setfield(L, -2, "PATH_INFO"); + } + + /* http protcol version */ + lua_pushnumber(L, 0.9 + (req->version / 10.0)); + lua_setfield(L, -2, "HTTP_VERSION"); + + lua_pushstring(L, http_versions[req->version]); + lua_setfield(L, -2, "SERVER_PROTOCOL"); + + + /* address information */ + lua_pushstring(L, sa_straddr(&cl->peeraddr)); + lua_setfield(L, -2, "REMOTE_ADDR"); + + lua_pushinteger(L, sa_port(&cl->peeraddr)); + lua_setfield(L, -2, "REMOTE_PORT"); + + lua_pushstring(L, sa_straddr(&cl->servaddr)); + lua_setfield(L, -2, "SERVER_ADDR"); + + lua_pushinteger(L, sa_port(&cl->servaddr)); + lua_setfield(L, -2, "SERVER_PORT"); + + /* essential env vars */ + foreach_header(i, req->headers) + { + if (!strcasecmp(req->headers[i], "Content-Length")) + { + content_length = atoi(req->headers[i+1]); + } + else if (!strcasecmp(req->headers[i], "Content-Type")) + { + lua_pushstring(L, req->headers[i+1]); + lua_setfield(L, -2, "CONTENT_TYPE"); + } + } + + lua_pushnumber(L, content_length); + lua_setfield(L, -2, "CONTENT_LENGTH"); + + /* misc. headers */ + lua_newtable(L); + + foreach_header(i, req->headers) + { + if( strcasecmp(req->headers[i], "Content-Length") && + strcasecmp(req->headers[i], "Content-Type")) + { + lua_pushstring(L, req->headers[i+1]); + lua_setfield(L, -2, req->headers[i]); + } + } + + lua_setfield(L, -2, "headers"); + + + /* call */ + switch (lua_pcall(L, 1, 0, 0)) + { + case LUA_ERRMEM: + case LUA_ERRRUN: + err_str = luaL_checkstring(L, -1); + + if (! err_str) + err_str = "Unknown error"; + + printf("%s 500 Internal Server Error\r\n" + "Connection: close\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: %i\r\n\r\n" + "Lua raised a runtime error:\n %s\n", + http_versions[req->version], + 31 + strlen(err_str), err_str); + + break; + + default: + break; + } + + close(wfd[0]); + close(rfd[1]); + exit(0); + + break; + + /* parent; handle I/O relaying */ + default: + memset(state, 0, sizeof(*state)); + + cl->rpipe.fd = rfd[0]; + cl->wpipe.fd = wfd[1]; + cl->proc.pid = child; + + /* make pipe non-blocking */ + fd_nonblock(cl->rpipe.fd); + fd_nonblock(cl->wpipe.fd); + + /* close unneeded pipe ends */ + close(rfd[1]); + close(wfd[0]); + + D("Lua: Child(%d) created: rfd(%d) wfd(%d)\n", child, rfd[0], wfd[1]); + + state->content_length = cl->httpbuf.len; + + /* find content length */ + if (req->method == UH_HTTP_MSG_POST) + { + foreach_header(i, req->headers) + { + if (!strcasecmp(req->headers[i], "Content-Length")) + { + state->content_length = atoi(req->headers[i+1]); + break; + } + } + } + + cl->cb = uh_lua_socket_cb; + cl->priv = state; + + break; + } + + return true; +} + +void uh_lua_close(lua_State *L) +{ + lua_close(L); +} |