Fixed: [PATCH 2/3] uhttpd URL-codec enhancements.
[openwrt/staging/mkresin.git] / package / uhttpd / src / uhttpd-lua.c
1 /*
2 * uhttpd - Tiny single-threaded httpd - Lua handler
3 *
4 * Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org>
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
19 #include "uhttpd.h"
20 #include "uhttpd-utils.h"
21 #include "uhttpd-lua.h"
22
23
24 static int uh_lua_recv(lua_State *L)
25 {
26 size_t length;
27 char buffer[UH_LIMIT_MSGHEAD];
28 ssize_t rlen = 0;
29 fd_set reader;
30 struct timeval timeout;
31
32 length = luaL_checknumber(L, 1);
33
34 if( (length > 0) && (length <= sizeof(buffer)) )
35 {
36 FD_ZERO(&reader);
37 FD_SET(fileno(stdin), &reader);
38
39 /* fail after 0.1s */
40 timeout.tv_sec = 0;
41 timeout.tv_usec = 100000;
42
43 /* check whether fd is readable */
44 if( select(fileno(stdin) + 1, &reader, NULL, NULL, &timeout) > 0 )
45 {
46 /* receive data */
47 rlen = read(fileno(stdin), buffer, length);
48 lua_pushnumber(L, rlen);
49
50 if( rlen > 0 )
51 {
52 lua_pushlstring(L, buffer, rlen);
53 return 2;
54 }
55
56 return 1;
57 }
58
59 /* no, timeout and actually no data */
60 lua_pushnumber(L, -2);
61 return 1;
62 }
63
64 /* parameter error */
65 lua_pushnumber(L, -3);
66 return 1;
67 }
68
69 static int uh_lua_send_common(lua_State *L, int chunked)
70 {
71 size_t length;
72 const char *buffer;
73 char chunk[16];
74 ssize_t slen = 0;
75
76 buffer = luaL_checklstring(L, 1, &length);
77
78 if( chunked )
79 {
80 if( length > 0 )
81 {
82 snprintf(chunk, sizeof(chunk), "%X\r\n", length);
83 slen = write(fileno(stdout), chunk, strlen(chunk));
84 slen += write(fileno(stdout), buffer, length);
85 slen += write(fileno(stdout), "\r\n", 2);
86 }
87 else
88 {
89 slen = write(fileno(stdout), "0\r\n\r\n", 5);
90 }
91 }
92 else
93 {
94 slen = write(fileno(stdout), buffer, length);
95 }
96
97 lua_pushnumber(L, slen);
98 return 1;
99 }
100
101 static int uh_lua_send(lua_State *L)
102 {
103 return uh_lua_send_common(L, 0);
104 }
105
106 static int uh_lua_sendc(lua_State *L)
107 {
108 return uh_lua_send_common(L, 1);
109 }
110
111 static int uh_lua_str2str(lua_State *L, int (*xlate_func) (char *, int, const char *, int))
112 {
113 size_t inlen;
114 int outlen;
115 const char *inbuf;
116 char outbuf[UH_LIMIT_MSGHEAD];
117
118 inbuf = luaL_checklstring(L, 1, &inlen);
119 outlen = (* xlate_func)(outbuf, sizeof(outbuf), inbuf, inlen);
120 if( outlen < 0 )
121 luaL_error( L, "%s on URL-encode codec",
122 (outlen==-1) ? "buffer overflow" : "malformed string" );
123
124 lua_pushlstring(L, outbuf, outlen);
125 return 1;
126 }
127
128 static int uh_lua_urldecode(lua_State *L)
129 {
130 return uh_lua_str2str( L, uh_urldecode );
131 }
132
133
134 static int uh_lua_urlencode(lua_State *L)
135 {
136 return uh_lua_str2str( L, uh_urlencode );
137 }
138
139
140 lua_State * uh_lua_init(const char *handler)
141 {
142 lua_State *L = lua_open();
143 const char *err_str = NULL;
144
145 /* Load standard libaries */
146 luaL_openlibs(L);
147
148 /* build uhttpd api table */
149 lua_newtable(L);
150
151 /* register global send and receive functions */
152 lua_pushcfunction(L, uh_lua_recv);
153 lua_setfield(L, -2, "recv");
154
155 lua_pushcfunction(L, uh_lua_send);
156 lua_setfield(L, -2, "send");
157
158 lua_pushcfunction(L, uh_lua_sendc);
159 lua_setfield(L, -2, "sendc");
160
161 lua_pushcfunction(L, uh_lua_urldecode);
162 lua_setfield(L, -2, "urldecode");
163
164 lua_pushcfunction(L, uh_lua_urlencode);
165 lua_setfield(L, -2, "urlencode");
166
167 /* _G.uhttpd = { ... } */
168 lua_setfield(L, LUA_GLOBALSINDEX, "uhttpd");
169
170
171 /* load Lua handler */
172 switch( luaL_loadfile(L, handler) )
173 {
174 case LUA_ERRSYNTAX:
175 fprintf(stderr,
176 "Lua handler contains syntax errors, unable to continue\n");
177 exit(1);
178
179 case LUA_ERRMEM:
180 fprintf(stderr,
181 "Lua handler ran out of memory, unable to continue\n");
182 exit(1);
183
184 case LUA_ERRFILE:
185 fprintf(stderr,
186 "Lua cannot open the handler script, unable to continue\n");
187 exit(1);
188
189 default:
190 /* compile Lua handler */
191 switch( lua_pcall(L, 0, 0, 0) )
192 {
193 case LUA_ERRRUN:
194 err_str = luaL_checkstring(L, -1);
195 fprintf(stderr,
196 "Lua handler had runtime error, unable to continue\n"
197 "Error: %s\n", err_str
198 );
199 exit(1);
200
201 case LUA_ERRMEM:
202 err_str = luaL_checkstring(L, -1);
203 fprintf(stderr,
204 "Lua handler ran out of memory, unable to continue\n"
205 "Error: %s\n", err_str
206 );
207 exit(1);
208
209 default:
210 /* test handler function */
211 lua_getglobal(L, UH_LUA_CALLBACK);
212
213 if( ! lua_isfunction(L, -1) )
214 {
215 fprintf(stderr,
216 "Lua handler provides no " UH_LUA_CALLBACK "(), unable to continue\n");
217 exit(1);
218 }
219
220 lua_pop(L, 1);
221 break;
222 }
223
224 break;
225 }
226
227 return L;
228 }
229
230 void uh_lua_request(struct client *cl, struct http_request *req, lua_State *L)
231 {
232 int i, data_sent;
233 int content_length = 0;
234 int buflen = 0;
235 int fd_max = 0;
236 char *query_string;
237 const char *prefix = cl->server->conf->lua_prefix;
238 const char *err_str = NULL;
239
240 int rfd[2] = { 0, 0 };
241 int wfd[2] = { 0, 0 };
242
243 char buf[UH_LIMIT_MSGHEAD];
244
245 pid_t child;
246
247 fd_set reader;
248 fd_set writer;
249
250 struct sigaction sa;
251 struct timeval timeout;
252
253
254 /* spawn pipes for me->child, child->me */
255 if( (pipe(rfd) < 0) || (pipe(wfd) < 0) )
256 {
257 uh_http_sendhf(cl, 500, "Internal Server Error",
258 "Failed to create pipe: %s", strerror(errno));
259
260 if( rfd[0] > 0 ) close(rfd[0]);
261 if( rfd[1] > 0 ) close(rfd[1]);
262 if( wfd[0] > 0 ) close(wfd[0]);
263 if( wfd[1] > 0 ) close(wfd[1]);
264
265 return;
266 }
267
268
269 switch( (child = fork()) )
270 {
271 case -1:
272 uh_http_sendhf(cl, 500, "Internal Server Error",
273 "Failed to fork child: %s", strerror(errno));
274 break;
275
276 case 0:
277 /* restore SIGTERM */
278 sa.sa_flags = 0;
279 sa.sa_handler = SIG_DFL;
280 sigemptyset(&sa.sa_mask);
281 sigaction(SIGTERM, &sa, NULL);
282
283 /* close loose pipe ends */
284 close(rfd[0]);
285 close(wfd[1]);
286
287 /* patch stdout and stdin to pipes */
288 dup2(rfd[1], 1);
289 dup2(wfd[0], 0);
290
291 /* put handler callback on stack */
292 lua_getglobal(L, UH_LUA_CALLBACK);
293
294 /* build env table */
295 lua_newtable(L);
296
297 /* request method */
298 switch(req->method)
299 {
300 case UH_HTTP_MSG_GET:
301 lua_pushstring(L, "GET");
302 break;
303
304 case UH_HTTP_MSG_HEAD:
305 lua_pushstring(L, "HEAD");
306 break;
307
308 case UH_HTTP_MSG_POST:
309 lua_pushstring(L, "POST");
310 break;
311 }
312
313 lua_setfield(L, -2, "REQUEST_METHOD");
314
315 /* request url */
316 lua_pushstring(L, req->url);
317 lua_setfield(L, -2, "REQUEST_URI");
318
319 /* script name */
320 lua_pushstring(L, cl->server->conf->lua_prefix);
321 lua_setfield(L, -2, "SCRIPT_NAME");
322
323 /* query string, path info */
324 if( (query_string = strchr(req->url, '?')) != NULL )
325 {
326 lua_pushstring(L, query_string + 1);
327 lua_setfield(L, -2, "QUERY_STRING");
328
329 if( (int)(query_string - req->url) > strlen(prefix) )
330 {
331 lua_pushlstring(L,
332 &req->url[strlen(prefix)],
333 (int)(query_string - req->url) - strlen(prefix)
334 );
335
336 lua_setfield(L, -2, "PATH_INFO");
337 }
338 }
339 else if( strlen(req->url) > strlen(prefix) )
340 {
341 lua_pushstring(L, &req->url[strlen(prefix)]);
342 lua_setfield(L, -2, "PATH_INFO");
343 }
344
345 /* http protcol version */
346 lua_pushnumber(L, floor(req->version * 10) / 10);
347 lua_setfield(L, -2, "HTTP_VERSION");
348
349 if( req->version > 1.0 )
350 lua_pushstring(L, "HTTP/1.1");
351 else
352 lua_pushstring(L, "HTTP/1.0");
353
354 lua_setfield(L, -2, "SERVER_PROTOCOL");
355
356
357 /* address information */
358 lua_pushstring(L, sa_straddr(&cl->peeraddr));
359 lua_setfield(L, -2, "REMOTE_ADDR");
360
361 lua_pushinteger(L, sa_port(&cl->peeraddr));
362 lua_setfield(L, -2, "REMOTE_PORT");
363
364 lua_pushstring(L, sa_straddr(&cl->servaddr));
365 lua_setfield(L, -2, "SERVER_ADDR");
366
367 lua_pushinteger(L, sa_port(&cl->servaddr));
368 lua_setfield(L, -2, "SERVER_PORT");
369
370 /* essential env vars */
371 foreach_header(i, req->headers)
372 {
373 if( !strcasecmp(req->headers[i], "Content-Length") )
374 {
375 lua_pushnumber(L, atoi(req->headers[i+1]));
376 lua_setfield(L, -2, "CONTENT_LENGTH");
377 }
378 else if( !strcasecmp(req->headers[i], "Content-Type") )
379 {
380 lua_pushstring(L, req->headers[i+1]);
381 lua_setfield(L, -2, "CONTENT_TYPE");
382 }
383 }
384
385 /* misc. headers */
386 lua_newtable(L);
387
388 foreach_header(i, req->headers)
389 {
390 if( strcasecmp(req->headers[i], "Content-Length") &&
391 strcasecmp(req->headers[i], "Content-Type")
392 ) {
393 lua_pushstring(L, req->headers[i+1]);
394 lua_setfield(L, -2, req->headers[i]);
395 }
396 }
397
398 lua_setfield(L, -2, "headers");
399
400
401 /* call */
402 switch( lua_pcall(L, 1, 0, 0) )
403 {
404 case LUA_ERRMEM:
405 case LUA_ERRRUN:
406 err_str = luaL_checkstring(L, -1);
407
408 if( ! err_str )
409 err_str = "Unknown error";
410
411 printf(
412 "HTTP/%.1f 500 Internal Server Error\r\n"
413 "Connection: close\r\n"
414 "Content-Type: text/plain\r\n"
415 "Content-Length: %i\r\n\r\n"
416 "Lua raised a runtime error:\n %s\n",
417 req->version, 31 + strlen(err_str), err_str
418 );
419
420 break;
421
422 default:
423 break;
424 }
425
426 close(wfd[0]);
427 close(rfd[1]);
428 exit(0);
429
430 break;
431
432 /* parent; handle I/O relaying */
433 default:
434 /* close unneeded pipe ends */
435 close(rfd[1]);
436 close(wfd[0]);
437
438 /* max watch fd */
439 fd_max = max(rfd[0], wfd[1]) + 1;
440
441 /* find content length */
442 if( req->method == UH_HTTP_MSG_POST )
443 {
444 foreach_header(i, req->headers)
445 {
446 if( ! strcasecmp(req->headers[i], "Content-Length") )
447 {
448 content_length = atoi(req->headers[i+1]);
449 break;
450 }
451 }
452 }
453
454
455 #define ensure(x) \
456 do { if( x < 0 ) goto out; } while(0)
457
458 data_sent = 0;
459
460 timeout.tv_sec = cl->server->conf->script_timeout;
461 timeout.tv_usec = 0;
462
463 /* I/O loop, watch our pipe ends and dispatch child reads/writes from/to socket */
464 while( 1 )
465 {
466 FD_ZERO(&reader);
467 FD_ZERO(&writer);
468
469 FD_SET(rfd[0], &reader);
470 FD_SET(wfd[1], &writer);
471
472 /* wait until we can read or write or both */
473 if( select_intr(fd_max, &reader,
474 (content_length > -1) ? &writer : NULL, NULL,
475 (data_sent < 1) ? &timeout : NULL) > 0
476 ) {
477 /* ready to write to Lua child */
478 if( FD_ISSET(wfd[1], &writer) )
479 {
480 /* there is unread post data waiting */
481 if( content_length > 0 )
482 {
483 /* read it from socket ... */
484 if( (buflen = uh_tcp_recv(cl, buf, min(content_length, sizeof(buf)))) > 0 )
485 {
486 /* ... and write it to child's stdin */
487 if( write(wfd[1], buf, buflen) < 0 )
488 perror("write()");
489
490 content_length -= buflen;
491 }
492
493 /* unexpected eof! */
494 else
495 {
496 if( write(wfd[1], "", 0) < 0 )
497 perror("write()");
498
499 content_length = 0;
500 }
501 }
502
503 /* there is no more post data, close pipe to child's stdin */
504 else if( content_length > -1 )
505 {
506 close(wfd[1]);
507 content_length = -1;
508 }
509 }
510
511 /* ready to read from Lua child */
512 if( FD_ISSET(rfd[0], &reader) )
513 {
514 /* read data from child ... */
515 if( (buflen = read(rfd[0], buf, sizeof(buf))) > 0 )
516 {
517 /* pass through buffer to socket */
518 ensure(uh_tcp_send(cl, buf, buflen));
519 data_sent = 1;
520 }
521
522 /* looks like eof from child */
523 else
524 {
525 /* error? */
526 if( ! data_sent )
527 uh_http_sendhf(cl, 500, "Internal Server Error",
528 "The Lua child did not produce any response");
529
530 break;
531 }
532 }
533 }
534
535 /* timeout exceeded or interrupted by SIGCHLD */
536 else
537 {
538 if( (errno != EINTR) && ! data_sent )
539 {
540 ensure(uh_http_sendhf(cl, 504, "Gateway Timeout",
541 "The Lua script took too long to produce "
542 "a response"));
543 }
544
545 break;
546 }
547 }
548
549 out:
550 close(rfd[0]);
551 close(wfd[1]);
552
553 if( !kill(child, 0) )
554 {
555 kill(child, SIGTERM);
556 waitpid(child, NULL, 0);
557 }
558
559 break;
560 }
561 }
562
563 void uh_lua_close(lua_State *L)
564 {
565 lua_close(L);
566 }