uhttpd: make Lua handler more CGI like and fork child away
authorJo-Philipp Wich <jow@openwrt.org>
Sun, 21 Mar 2010 17:50:00 +0000 (17:50 +0000)
committerJo-Philipp Wich <jow@openwrt.org>
Sun, 21 Mar 2010 17:50:00 +0000 (17:50 +0000)
13 files changed:
contrib/package/uhttpd/src/uhttpd-cgi.c
contrib/package/uhttpd/src/uhttpd-cgi.h
contrib/package/uhttpd/src/uhttpd-file.c
contrib/package/uhttpd/src/uhttpd-file.h
contrib/package/uhttpd/src/uhttpd-lua.c
contrib/package/uhttpd/src/uhttpd-lua.h
contrib/package/uhttpd/src/uhttpd-mimetypes.h
contrib/package/uhttpd/src/uhttpd-tls.c
contrib/package/uhttpd/src/uhttpd-tls.h
contrib/package/uhttpd/src/uhttpd-utils.c
contrib/package/uhttpd/src/uhttpd-utils.h
contrib/package/uhttpd/src/uhttpd.c
contrib/package/uhttpd/src/uhttpd.h

index 5df045e..a6e5d99 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * uhttpd - Tiny non-forking httpd - CGI handler
+ * uhttpd - Tiny single-threaded httpd - CGI handler
  *
  *   Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org>
  *
index 6b9c9c6..c90557d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * uhttpd - Tiny non-forking httpd - CGI header
+ * uhttpd - Tiny single-threaded httpd - CGI header
  *
  *   Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org>
  *
index 7ce5e39..2a06f85 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * uhttpd - Tiny non-forking httpd - Static file handler
+ * uhttpd - Tiny single-threaded httpd - Static file handler
  *
  *   Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org>
  *
index 623820c..3d46815 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * uhttpd - Tiny non-forking httpd - Static file header
+ * uhttpd - Tiny single-threaded httpd - Static file header
  *
  *   Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org>
  *
index 2c2821d..e88c42e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * uhttpd - Tiny non-forking httpd - Lua handler
+ * uhttpd - Tiny single-threaded httpd - Lua handler
  *
  *   Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org>
  *
@@ -28,37 +28,23 @@ static int uh_lua_recv(lua_State *L)
        ssize_t rlen = 0;
        fd_set reader;
        struct timeval timeout;
-       struct client *cl;
 
-       luaL_checktype(L, 1, LUA_TLIGHTUSERDATA);
-       cl = (struct client *) lua_topointer(L, 1);
-       length = luaL_checknumber(L, 2);
+       length = luaL_checknumber(L, 1);
 
-       if( (cl != NULL) && (length > 0) && (length <= sizeof(buffer)) )
+       if( (length > 0) && (length <= sizeof(buffer)) )
        {
                FD_ZERO(&reader);
-               FD_SET(cl->socket, &reader);
+               FD_SET(fileno(stdin), &reader);
 
                /* fail after 0.1s */
                timeout.tv_sec  = 0;
                timeout.tv_usec = 100000;
 
-               /* first return stuff from peek buffer */
-               if( cl->peeklen > 0 )
-               {
-                       /* receive data */
-                       rlen = uh_tcp_recv(cl, buffer, min(cl->peeklen, length));
-                       lua_pushnumber(L, rlen);
-                       lua_pushlstring(L, buffer, rlen);
-
-                       return 2;
-               }
-
                /* check whether fd is readable */
-               else if( select(cl->socket + 1, &reader, NULL, NULL, &timeout) > 0 )
+               if( select(fileno(stdin) + 1, &reader, NULL, NULL, &timeout) > 0 )
                {
                        /* receive data */
-                       rlen = uh_tcp_recv(cl, buffer, length);
+                       rlen = read(fileno(stdin), buffer, length);
                        lua_pushnumber(L, rlen);
 
                        if( rlen > 0 )
@@ -84,25 +70,31 @@ static int uh_lua_send_common(lua_State *L, int chunked)
 {
        size_t length;
        const char *buffer;
+       char chunk[16];
        ssize_t slen = 0;
-       struct client *cl;
 
-       luaL_checktype(L, 1, LUA_TLIGHTUSERDATA);
-       cl = (struct client *) lua_topointer(L, 1);
-       buffer = luaL_checklstring(L, 2, &length);
+       buffer = luaL_checklstring(L, 1, &length);
 
-       if( (cl != NULL) && (length > 0) )
+       if( chunked )
        {
-               if( chunked )
-                       slen = uh_http_sendc(cl, buffer, length);
+               if( length > 0 )
+               {
+                       snprintf(chunk, sizeof(chunk), "%X\r\n", length);
+                       slen =  write(fileno(stdout), chunk, strlen(chunk));
+                       slen += write(fileno(stdout), buffer, length);
+                       slen += write(fileno(stdout), "\r\n", 2);
+               }
                else
-                       slen = uh_tcp_send(cl, buffer, length);
-
-               lua_pushnumber(L, slen);
-               return 1;
+               {
+                       slen = write(fileno(stdout), "0\r\n\r\n", 5);
+               }
+       }
+       else
+       {
+               slen = write(fileno(stdout), buffer, length);
        }
 
-       lua_pushnumber(L, -1);
+       lua_pushnumber(L, slen);
        return 1;
 }
 
@@ -133,26 +125,10 @@ static int uh_lua_urldecode(lua_State *L)
 lua_State * uh_lua_init(const char *handler)
 {
        lua_State *L = lua_open();
-       const luaL_reg *lib;
        const char *err_str = NULL;
 
-       /* Declare the Lua libraries we wish to use. */
-       /* Note: If you are opening and running a file containing Lua code */
-       /* using 'lua_dofile(l, "myfile.lua") - you must delcare all the libraries */
-       /* used in that file here also. */
-       static const luaL_reg lualibs[] =
-       {
-               { "base",       luaopen_base },
-                       { "string",             luaopen_string },
-               { NULL,         NULL }
-       };
-
-       /* preload libraries */
-       for (lib = lualibs; lib->func != NULL; lib++)
-       {
-               lib->func(L);
-               lua_settop(L, 0);
-       }
+       /* Load standard libaries */
+       luaL_openlibs(L);
 
        /* build uhttpd api table */
        lua_newtable(L);
@@ -235,113 +211,314 @@ lua_State * uh_lua_init(const char *handler)
 
 void uh_lua_request(struct client *cl, struct http_request *req, lua_State *L)
 {
-       int i;
+       pid_t pid;
+
+       int i, data_sent;
+       int content_length = 0;
+       int buflen = 0;
+       int fd_max = 0;
        char *query_string;
        const char *prefix = cl->server->conf->lua_prefix;
        const char *err_str = NULL;
 
-       /* put handler callback on stack */
-       lua_getglobal(L, UH_LUA_CALLBACK);
+       int rfd[2] = { 0, 0 };
+       int wfd[2] = { 0, 0 };
 
+       char buf[UH_LIMIT_MSGHEAD];
 
-       /* build env table */
-       lua_newtable(L);
+       fd_set reader;
+       fd_set writer;
+
+       struct timeval timeout;
 
-       /* client object */
-       lua_pushlightuserdata(L, (void *)cl);
-       lua_setfield(L, -2, "client");
 
-       /* request method */
-       switch(req->method)
+       /* spawn pipes for me->child, child->me */
+       if( (pipe(rfd) < 0) || (pipe(wfd) < 0) )
        {
-               case UH_HTTP_MSG_GET:
-                       lua_pushstring(L, "get");
-                       break;
+               uh_http_sendhf(cl, 500, "Internal Server Error",
+                       "Failed to create pipe: %s", strerror(errno));
 
-               case UH_HTTP_MSG_HEAD:
-                       lua_pushstring(L, "head");
-                       break;
+               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]);
 
-               case UH_HTTP_MSG_POST:
-                       lua_pushstring(L, "post");
-                       break;
+               return;
        }
 
-       lua_setfield(L, -2, "request_method");
 
-       /* request url */
-       lua_pushstring(L, req->url);
-       lua_setfield(L, -2, "request_url");
-
-       /* query string, path info */
-       if( (query_string = strchr(req->url, '?')) != NULL )
+       switch( (pid = fork()) )
        {
-               lua_pushstring(L, query_string + 1);
-               lua_setfield(L, -2, "query_string");
+               case -1:
+                       uh_http_sendhf(cl, 500, "Internal Server Error",
+                               "Failed to fork child: %s", strerror(errno));
+                       break;
 
-               if( (int)(query_string - req->url) > strlen(prefix) )
-               {
-                       lua_pushlstring(L,
-                               &req->url[strlen(prefix)],
-                               (int)(query_string - req->url) - strlen(prefix)
-                       );
+               case 0:
+                       /* child */
+                       close(rfd[0]);
+                       close(wfd[1]);
 
-                       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");
-       }
+                       /* patch stdout and stdin to pipes */
+                       dup2(rfd[1], 1);
+                       dup2(wfd[0], 0);
 
-       /* http protcol version */
-       lua_pushnumber(L, floor(req->version * 10) / 10);
-       lua_setfield(L, -2, "http_version");
+                       /* put handler callback on stack */
+                       lua_getglobal(L, UH_LUA_CALLBACK);
 
+                       /* build env table */
+                       lua_newtable(L);
 
-       /* address information */
-       lua_pushstring(L, sa_straddr(&cl->peeraddr));
-       lua_setfield(L, -2, "remote_addr");
+                       /* request method */
+                       switch(req->method)
+                       {
+                               case UH_HTTP_MSG_GET:
+                                       lua_pushstring(L, "GET");
+                                       break;
 
-       lua_pushinteger(L, sa_port(&cl->peeraddr));
-       lua_setfield(L, -2, "remote_port");
+                               case UH_HTTP_MSG_HEAD:
+                                       lua_pushstring(L, "HEAD");
+                                       break;
 
-       lua_pushstring(L, sa_straddr(&cl->servaddr));
-       lua_setfield(L, -2, "server_addr");
+                               case UH_HTTP_MSG_POST:
+                                       lua_pushstring(L, "POST");
+                                       break;
+                       }
 
-       lua_pushinteger(L, sa_port(&cl->servaddr));
-       lua_setfield(L, -2, "server_port");
+                       lua_setfield(L, -2, "REQUEST_METHOD");
 
+                       /* request url */
+                       lua_pushstring(L, req->url);
+                       lua_setfield(L, -2, "REQUEST_URI");
 
-       /* headers */
-       lua_newtable(L);
+                       /* script name */
+                       lua_pushstring(L, cl->server->conf->lua_prefix);
+                       lua_setfield(L, -2, "SCRIPT_NAME");
 
-       foreach_header(i, req->headers)
-       {
-               lua_pushstring(L, req->headers[i+1]);
-               lua_setfield(L, -2, req->headers[i]);
-       }
+                       /* 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, "headers");
+                                       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, floor(req->version * 10) / 10);
+                       lua_setfield(L, -2, "HTTP_VERSION");
 
-       /* call */
-       switch( lua_pcall(L, 1, 0, 0) )
-       {
-               case LUA_ERRRUN:
-                       err_str = luaL_checkstring(L, -1);
-                       uh_http_sendhf(cl, 500, "Lua runtime error",
-                               "Lua raised an error:\n%s\n", err_str);
-                       break;
+                       if( req->version > 1.0 )
+                               lua_pushstring(L, "HTTP/1.1");
+                       else
+                               lua_pushstring(L, "HTTP/1.0");
+
+                       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") )
+                               {
+                                       lua_pushnumber(L, atoi(req->headers[i+1]));
+                                       lua_setfield(L, -2, "CONTENT_LENGTH");
+                               }
+                               else if( !strcasecmp(req->headers[i], "Content-Type") )
+                               {
+                                       lua_pushstring(L, req->headers[i+1]);
+                                       lua_setfield(L, -2, "CONTENT_TYPE");
+                               }
+                       }
+
+                       /* 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(
+                                               "HTTP/%.1f 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",
+                                                       req->version, 31 + strlen(err_str), err_str
+                                       );
+
+                                       break;
+
+                               default:
+                                       break;
+                       }
+
+                       close(wfd[0]);
+                       close(rfd[1]);
+                       exit(0);
 
-               case LUA_ERRMEM:
-                       err_str = luaL_checkstring(L, -1);
-                       uh_http_sendhf(cl, 500, "Lua out of memory",
-                               "Lua raised an error:\n%s\n", err_str);
                        break;
 
+               /* parent; handle I/O relaying */
                default:
+                       /* close unneeded pipe ends */
+                       close(rfd[1]);
+                       close(wfd[0]);
+
+                       /* max watch fd */
+                       fd_max = max(rfd[0], wfd[1]) + 1;
+
+                       /* find content length */
+                       if( req->method == UH_HTTP_MSG_POST )
+                       {
+                               foreach_header(i, req->headers)
+                               {
+                                       if( ! strcasecmp(req->headers[i], "Content-Length") )
+                                       {
+                                               content_length = atoi(req->headers[i+1]);
+                                               break;
+                                       }
+                               }
+                       }
+
+
+#define ensure(x) \
+       do { if( x < 0 ) goto out; } while(0)
+
+                       data_sent = 0;
+
+                       /* I/O loop, watch our pipe ends and dispatch child reads/writes from/to socket */
+                       while( 1 )
+                       {
+                               FD_ZERO(&reader);
+                               FD_ZERO(&writer);
+
+                               FD_SET(rfd[0], &reader);
+                               FD_SET(wfd[1], &writer);
+
+                               timeout.tv_sec = 15;
+                               timeout.tv_usec = 0;
+
+                               /* wait until we can read or write or both */
+                               if( select(fd_max, &reader, (content_length > -1) ? &writer : NULL, NULL, &timeout) > 0 )
+                               {
+                                       /* ready to write to Lua child */
+                                       if( FD_ISSET(wfd[1], &writer) )
+                                       {
+                                               /* there is unread post data waiting */
+                                               if( content_length > 0 )
+                                               {
+                                                       /* read it from socket ... */
+                                                       if( (buflen = uh_tcp_recv(cl, buf, min(content_length, sizeof(buf)))) > 0 )
+                                                       {
+                                                               /* ... and write it to child's stdin */
+                                                               if( write(wfd[1], buf, buflen) < 0 )
+                                                                       perror("write()");
+
+                                                               content_length -= buflen;
+                                                       }
+
+                                                       /* unexpected eof! */
+                                                       else
+                                                       {
+                                                               if( write(wfd[1], "", 0) < 0 )
+                                                                       perror("write()");
+
+                                                               content_length = 0;
+                                                       }
+                                               }
+
+                                               /* there is no more post data, close pipe to child's stdin */
+                                               else if( content_length > -1 )
+                                               {
+                                                       close(wfd[1]);
+                                                       content_length = -1;
+                                               }
+                                       }
+
+                                       /* ready to read from Lua child */
+                                       if( FD_ISSET(rfd[0], &reader) )
+                                       {
+                                               /* read data from child ... */
+                                               if( (buflen = read(rfd[0], buf, sizeof(buf))) > 0 )
+                                               {
+                                                       /* pass through buffer to socket */
+                                                       ensure(uh_tcp_send(cl, buf, buflen));
+                                                       data_sent = 1;
+                                               }
+
+                                               /* looks like eof from child */
+                                               else
+                                               {
+                                                       /* error? */
+                                                       if( ! data_sent )
+                                                               uh_http_sendhf(cl, 500, "Internal Server Error",
+                                                                       "The Lua child did not produce any response");
+
+                                                       break;
+                                               }
+                                       }
+                               }
+
+                               /* no activity for 15 seconds... looks dead */
+                               else
+                               {
+                                       ensure(uh_http_sendhf(cl, 504, "Gateway Timeout",
+                                               "The Lua handler took too long to produce a response"));
+
+                                       break;
+                               }
+                       }
+
+               out:
+                       close(rfd[0]);
+                       close(wfd[1]);
+
                        break;
        }
 }
index 22a44c6..518c2bc 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * uhttpd - Tiny non-forking httpd - Lua header
+ * uhttpd - Tiny single-threaded httpd - Lua header
  *
  *   Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org>
  *
@@ -19,6 +19,7 @@
 #ifndef _UHTTPD_LUA_
 
 #include <math.h>  /* floor() */
+#include <errno.h>
 
 #include <lua.h>
 #include <lauxlib.h>
index 49e8f15..1c93f31 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * uhttpd - Tiny non-forking httpd - MIME type definitions
+ * uhttpd - Tiny single-threaded httpd - MIME type definitions
  *
  *   Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org>
  *
index c35d3aa..bbec750 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * uhttpd - Tiny non-forking httpd - TLS helper
+ * uhttpd - Tiny single-threaded httpd - TLS helper
  *
  *   Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org>
  *
index f5412a4..2de2d26 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * uhttpd - Tiny non-forking httpd - TLS header
+ * uhttpd - Tiny single-threaded httpd - TLS header
  *
  *   Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org>
  *
index 549ad88..01cfa12 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * uhttpd - Tiny non-forking httpd - Utility functions
+ * uhttpd - Tiny single-threaded httpd - Utility functions
  *
  *   Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org>
  *
index da82ab0..c7a6c90 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * uhttpd - Tiny non-forking httpd - Utility header
+ * uhttpd - Tiny single-threaded httpd - Utility header
  *
  *   Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org>
  *
index 7886060..ea4ca00 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * uhttpd - Tiny non-forking httpd - Main component
+ * uhttpd - Tiny single-threaded httpd - Main component
  *
  *   Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org>
  *
@@ -372,6 +372,19 @@ static struct http_request * uh_http_header_recv(struct client *cl)
        return NULL;
 }
 
+static int uh_path_match(const char *prefix, const char *url)
+{
+       if( (strstr(url, prefix) == url) &&
+           ((prefix[strlen(prefix)-1] == '/') ||
+                (strlen(url) == strlen(prefix))   ||
+                (url[strlen(prefix)] == '/'))
+       ) {
+               return 1;
+       }
+
+       return 0;
+}
+
 
 int main (int argc, char **argv)
 {
@@ -735,7 +748,7 @@ int main (int argc, char **argv)
                                        {
 #ifdef HAVE_LUA
                                                /* Lua request? */
-                                               if( L && strstr(req->url, conf.lua_prefix) == req->url )
+                                               if( L && uh_path_match(conf.lua_prefix, req->url) )
                                                {
                                                        uh_lua_request(cl, req, L);
                                                }
@@ -748,7 +761,7 @@ int main (int argc, char **argv)
                                                        if( uh_auth_check(cl, req, pin) )
                                                        {
 #ifdef HAVE_CGI
-                                                               if( strstr(pin->name, conf.cgi_prefix) == pin->name )
+                                                               if( uh_path_match(conf.cgi_prefix, pin->name) )
                                                                {
                                                                        uh_cgi_request(cl, req, pin);
                                                                }
index eca3580..d26835a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * uhttpd - Tiny non-forking httpd - Main header
+ * uhttpd - Tiny single-threaded httpd - Main header
  *
  *   Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org>
  *