diff options
Diffstat (limited to 'package/uhttpd/src/uhttpd-lua.c')
| -rw-r--r-- | package/uhttpd/src/uhttpd-lua.c | 541 | 
1 files changed, 541 insertions, 0 deletions
| diff --git a/package/uhttpd/src/uhttpd-lua.c b/package/uhttpd/src/uhttpd-lua.c new file mode 100644 index 000000000..ab09841cd --- /dev/null +++ b/package/uhttpd/src/uhttpd-lua.c @@ -0,0 +1,541 @@ +/* + * uhttpd - Tiny single-threaded httpd - Lua handler + * + *   Copyright (C) 2010 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]; +	ssize_t rlen = 0; +	fd_set reader; +	struct timeval timeout; + +	length = luaL_checknumber(L, 1); + +	if( (length > 0) && (length <= sizeof(buffer)) ) +	{ +		FD_ZERO(&reader); +		FD_SET(fileno(stdin), &reader); + +		/* fail after 0.1s */ +		timeout.tv_sec  = 0; +		timeout.tv_usec = 100000; + +		/* check whether fd is readable */ +		if( select(fileno(stdin) + 1, &reader, NULL, NULL, &timeout) > 0 ) +		{ +			/* receive data */ +			rlen = read(fileno(stdin), buffer, length); +			lua_pushnumber(L, rlen); + +			if( rlen > 0 ) +			{ +				lua_pushlstring(L, buffer, rlen); +				return 2; +			} + +			return 1; +		} + +		/* no, timeout and actually no data */ +		lua_pushnumber(L, -2); +		return 1; +	} + +	/* parameter error */ +	lua_pushnumber(L, -3); +	return 1; +} + +static int uh_lua_send_common(lua_State *L, int chunked) +{ +	size_t length; +	const char *buffer; +	char chunk[16]; +	ssize_t slen = 0; + +	buffer = luaL_checklstring(L, 1, &length); + +	if( chunked ) +	{ +		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 = write(fileno(stdout), "0\r\n\r\n", 5); +		} +	} +	else +	{ +		slen = write(fileno(stdout), buffer, length); +	} + +	lua_pushnumber(L, slen); +	return 1; +} + +static int uh_lua_send(lua_State *L) +{ +	return uh_lua_send_common(L, 0); +} + +static int uh_lua_sendc(lua_State *L) +{ +	return uh_lua_send_common(L, 1); +} + +static int uh_lua_urldecode(lua_State *L) +{ +	size_t inlen, outlen; +	const char *inbuf; +	char outbuf[UH_LIMIT_MSGHEAD]; + +	inbuf = luaL_checklstring(L, 1, &inlen); +	outlen = uh_urldecode(outbuf, sizeof(outbuf), inbuf, inlen); + +	lua_pushlstring(L, outbuf, outlen); +	return 1; +} + + +lua_State * uh_lua_init(const char *handler) +{ +	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"); + +	/* _G.uhttpd = { ... } */ +	lua_setfield(L, LUA_GLOBALSINDEX, "uhttpd"); + + +	/* load Lua handler */ +	switch( luaL_loadfile(L, 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; +} + +void uh_lua_request(struct client *cl, struct http_request *req, lua_State *L) +{ +	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; + +	int rfd[2] = { 0, 0 }; +	int wfd[2] = { 0, 0 }; + +	char buf[UH_LIMIT_MSGHEAD]; + +	pid_t child; + +	fd_set reader; +	fd_set writer; + +	struct sigaction sa; +	struct timeval timeout; + + +	/* spawn pipes for me->child, child->me */ +	if( (pipe(rfd) < 0) || (pipe(wfd) < 0) ) +	{ +		uh_http_sendhf(cl, 500, "Internal Server Error", +			"Failed to create pipe: %s", strerror(errno)); + +		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]); + +		return; +	} + + +	switch( (child = fork()) ) +	{ +		case -1: +			uh_http_sendhf(cl, 500, "Internal Server Error", +				"Failed to fork child: %s", strerror(errno)); +			break; + +		case 0: +			/* restore SIGTERM */ +			sa.sa_flags = 0; +			sa.sa_handler = SIG_DFL; +			sigemptyset(&sa.sa_mask); +			sigaction(SIGTERM, &sa, NULL); + +			/* close loose pipe ends */ +			close(rfd[0]); +			close(wfd[1]); + +			/* patch stdout and stdin to pipes */ +			dup2(rfd[1], 1); +			dup2(wfd[0], 0); + +			/* put handler callback on stack */ +			lua_getglobal(L, UH_LUA_CALLBACK); + +			/* build env table */ +			lua_newtable(L); + +			/* request method */ +			switch(req->method) +			{ +				case UH_HTTP_MSG_GET: +					lua_pushstring(L, "GET"); +					break; + +				case UH_HTTP_MSG_HEAD: +					lua_pushstring(L, "HEAD"); +					break; + +				case UH_HTTP_MSG_POST: +					lua_pushstring(L, "POST"); +					break; +			} + +			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, floor(req->version * 10) / 10); +			lua_setfield(L, -2, "HTTP_VERSION"); + +			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); + +			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]); + +			if( !kill(child, 0) ) +				kill(child, SIGTERM); + +			break; +	} +} + +void uh_lua_close(lua_State *L) +{ +	lua_close(L); +} + + | 
