1 /**
2 * Copyright 2010 Christian Liesch
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 /**
18 * @file
19 *
20 * @Author christian liesch <liesch@gmx.ch>
21 *
22 * Implementation of the HTTP Test Tool Lua Extention
23 */
24
25 /************************************************************************
26 * Includes
27 ***********************************************************************/
28 #define LUA_COMPAT_MODULE
29 #include <lua.h>
30 #include <lualib.h>
31 #include <lauxlib.h>
32
33 #include <apr_sha1.h>
34
35 #include "lua_crypto.h"
36 #include "module.h"
37
38 /************************************************************************
39 * Definitions
40 ***********************************************************************/
41 const char * lua_module = "lua_module";
42
43 typedef struct lua_wconf_s {
44 int starting_line_nr;
45 apr_table_t *params;
46 apr_table_t *retvars;
47 lua_State *L;
48 } lua_wconf_t;
49
50 typedef struct lua_gconf_s {
51 int do_read_line;
52 } lua_gconf_t;
53
54 typedef struct lua_reader_s {
55 apr_pool_t *pool;
56 apr_table_t *lines;
57 int i;
58 int newline;
59 int starting_line_nr;
60 } lua_reader_t;
61
62 /************************************************************************
63 * Private
64 ***********************************************************************/
65 /**
66 * Get lua config from worker
67 *
68 * @param worker IN worker
69 * @return lua worker config
70 */
lua_get_worker_config(worker_t * worker)71 static lua_wconf_t *lua_get_worker_config(worker_t *worker) {
72 lua_wconf_t *wconf = module_get_config(worker->config, lua_module);
73 if (wconf == NULL) {
74 wconf = apr_pcalloc(worker->pbody, sizeof(*wconf));
75 wconf->params = apr_table_make(worker->pbody, 5);
76 wconf->retvars = apr_table_make(worker->pbody, 5);
77 module_set_config(worker->config, apr_pstrdup(worker->pbody, lua_module), wconf);
78 }
79 return wconf;
80 }
81
82 /**
83 * Get lua config from global
84 *
85 * @param global IN
86 * @return lua config
87 */
lua_get_global_config(global_t * global)88 static lua_gconf_t *lua_get_global_config(global_t *global) {
89 lua_gconf_t *gconf = module_get_config(global->config, lua_module);
90 if (gconf == NULL) {
91 gconf = apr_pcalloc(global->pool, sizeof(*gconf));
92 module_set_config(global->config, apr_pstrdup(global->pool, lua_module), gconf);
93 }
94 return gconf;
95 }
96
97 /**
98 * Get a new lua reader instance
99 * @param worker IN callee
100 * @param pool IN
101 * @return lua reader instance
102 */
lua_new_lua_reader(worker_t * worker,apr_pool_t * pool)103 static lua_reader_t *lua_new_lua_reader(worker_t *worker, apr_pool_t *pool) {
104 lua_wconf_t *wconf = lua_get_worker_config(worker->block);
105 lua_reader_t *reader = apr_pcalloc(pool, sizeof(*reader));
106 reader->pool = pool;
107 reader->lines = worker->lines;
108 reader->starting_line_nr = wconf->starting_line_nr;
109 return reader;
110 }
111
112 /**
113 * A simple lua line reader
114 * @param L in lua state
115 * @param ud IN user data
116 * @param size OUT len of string
117 * @return line
118 */
lua_get_line(lua_State * L,void * ud,size_t * size)119 static const char *lua_get_line(lua_State *L, void *ud, size_t *size) {
120 lua_reader_t *reader = ud;
121 apr_table_entry_t * e;
122
123 e = (apr_table_entry_t *) apr_table_elts(reader->lines)->elts;
124 if (reader->starting_line_nr) {
125 --reader->starting_line_nr;
126 *size = 1;
127 return apr_pstrdup(reader->pool, "\n");
128 }
129 if (reader->i < apr_table_elts(reader->lines)->nelts) {
130 if (reader->newline) {
131 reader->newline = 0;
132 *size = 1;
133 return apr_pstrdup(reader->pool, "\n");
134 }
135 else {
136 const char *line = e[reader->i].val;
137 *size = strlen(line);
138 ++reader->i;
139 reader->newline = 1;
140 return line;
141 }
142 }
143 else {
144 return NULL;
145 }
146 }
147
148 /**
149 * Do push the httest version on the stack
150 * @lua_return version as a string
151 * @param L IN lua state
152 * @return 1
153 */
luam_version(lua_State * L)154 static int luam_version(lua_State *L) {
155 lua_pushstring(L, PACKAGE_VERSION);
156 return 1;
157 }
158
159 /**
160 * Execute httest script.
161 * @param L IN lua state
162 * @return 0
163 */
luam_interpret(lua_State * L)164 static int luam_interpret(lua_State *L) {
165 apr_status_t status;
166 apr_pool_t *ptmp;
167 worker_t *worker;
168 worker_t *parent;
169 worker_t *call;
170 const char *string;
171 apr_table_t *lines;
172 char *buffer;
173 char *last;
174 char *line;
175 apr_size_t len;
176
177 if (!lua_isstring(L, -1)) {
178 luaL_error(L, "Expect a string to interpret");
179 return 1;
180 }
181
182 string = lua_tolstring(L, -1, &len);
183 lua_pop(L, 1);
184
185 lua_getfield(L, LUA_REGISTRYINDEX, "htt_worker");
186 worker = lua_touserdata(L, 1);
187 lua_pop(L, 1);
188
189 lua_getfield(L, LUA_REGISTRYINDEX, "htt_parent");
190 parent = lua_touserdata(L, 1);
191 lua_pop(L, 1);
192
193 HT_POOL_CREATE(&ptmp);
194
195 lines = apr_table_make(ptmp, 5);
196
197 call = apr_pcalloc(ptmp, sizeof(*call));
198 memcpy(call, worker, sizeof(*call));
199
200 buffer = apr_pcalloc(ptmp, len+1);
201 memcpy(buffer, string, len);
202
203 line = apr_strtok(buffer, "\n", &last);
204 while (line) {
205 while (*line == ' ') { ++line ; }
206 if (*line != '\0') {
207 apr_table_add(lines, "lua inline", line);
208 }
209 line = apr_strtok(NULL, "\n", &last);
210 }
211
212 call->lines = lines;
213 call->interpret = parent->interpret;
214
215 if ((status = call->interpret(call, worker, ptmp)) != APR_SUCCESS) {
216 luaL_error(L, "Error: %s(%d)", my_status_str(ptmp, status), status);
217 return 1;
218 }
219 apr_pool_destroy(ptmp);
220
221 return 0;
222 }
223
224 /**
225 * Get variable from httest
226 * @param L IN lua state
227 * @return 0
228 */
luam_getvar(lua_State * L)229 static int luam_getvar(lua_State *L) {
230 worker_t *worker;
231 const char *val;
232
233 const char *var = lua_tostring(L, -1);
234
235 lua_pop(L, 1);
236
237 lua_getfield(L, LUA_REGISTRYINDEX, "htt_worker");
238 worker = lua_touserdata(L, 1);
239
240 if ((val = worker_var_get(worker, var))) {
241 lua_pushstring(L, val);
242 return 1;
243 }
244
245 return 0;
246 }
247
248 /**
249 * Get transport object.
250 * @param L IN lua state
251 * @return 0
252 */
luam_transport_get(lua_State * L)253 static int luam_transport_get(lua_State *L) {
254 worker_t *worker;
255
256 lua_getfield(L, LUA_REGISTRYINDEX, "htt_worker");
257 worker = lua_touserdata(L, 1);
258
259 if (!worker->socket || !worker->socket->transport) {
260 lua_pushnil(L);
261 return 1;
262 }
263
264 lua_pushlightuserdata(L, worker->socket->transport);
265
266 luaL_getmetatable(L, "htt.transport");
267 lua_setmetatable(L, -2);
268
269 return 1;
270 }
271
272 /**
273 * Get transport object.
274 * @param L IN lua state
275 * @return 0
276 */
lua_checktransport(lua_State * L)277 static transport_t *lua_checktransport (lua_State *L) {
278 void *ud = luaL_checkudata(L, 1, "htt.transport");
279 luaL_argcheck(L, ud != NULL, 1, "`transport' expected");
280 return (transport_t *)ud;
281 }
282
283 /**
284 * Transport read method
285 * @param L IN lua state
286 * @return 0
287 */
luam_transport_read(lua_State * L)288 static int luam_transport_read(lua_State *L) {
289 if (lua_isnumber(L, -1)) {
290 apr_status_t status;
291 apr_pool_t *pool;
292 apr_size_t bytes;
293 transport_t *transport;
294 char *buffer;
295
296 bytes = lua_tointeger(L, -1);
297 transport = lua_checktransport(L);
298 HT_POOL_CREATE(&pool);
299 buffer = apr_pcalloc(pool, bytes);
300 if ((status = transport_read(transport, buffer, &bytes)) != APR_SUCCESS) {
301 lua_pushnil(L);
302 apr_pool_destroy(pool);
303 return 1;
304 }
305 lua_pushlstring(L, buffer, bytes);
306 apr_pool_destroy(pool);
307 return 1;
308 }
309 else {
310 luaL_error(L, "Expect number of bytes");
311 return 1;
312 }
313 }
314
315 /**
316 * Transport write method
317 * @param L IN lua state
318 * @return 0
319 */
luam_transport_write(lua_State * L)320 static int luam_transport_write(lua_State *L) {
321 if (lua_isstring(L, -1)) {
322 apr_status_t status;
323 apr_size_t bytes;
324 const char *buffer = lua_tolstring(L, -1, &bytes);
325 transport_t *transport = lua_checktransport(L);
326 if ((status = transport_write(transport, buffer, bytes)) != APR_SUCCESS) {
327 luaL_error(L, "Could not write %d bytes", bytes);
328 return 1;
329 }
330 }
331 return 0;
332 }
333
334 /**
335 * Transport set timeout method
336 * @param L IN lua state
337 * @return 0
338 */
luam_transport_set_timeout(lua_State * L)339 static int luam_transport_set_timeout(lua_State *L) {
340 if (lua_isnumber(L, -1)) {
341 apr_status_t status;
342 apr_interval_time_t tmo = lua_tointeger(L, -1);
343 transport_t *transport = lua_checktransport(L);
344 if ((status = transport_set_timeout(transport, tmo * 1000)) != APR_SUCCESS) {
345 luaL_error(L, "Could not timeout %d ms", tmo);
346 return 1;
347 }
348 }
349 return 0;
350 }
351
352 /**
353 * Transport get timeout method
354 * @param L IN lua state
355 * @return 1
356 */
luam_transport_get_timeout(lua_State * L)357 static int luam_transport_get_timeout(lua_State *L) {
358 apr_status_t status;
359 apr_interval_time_t tmo;
360 transport_t *transport = lua_checktransport(L);
361 if ((status = transport_get_timeout(transport, &tmo)) != APR_SUCCESS) {
362 luaL_error(L, "Could not get timeout");
363 return 1;
364 }
365 else {
366 lua_pushinteger(L, tmo/1000);
367 return 1;
368 }
369 }
370
371 /**
372 * Set of htt commands for lua
373 */
374 static const struct luaL_Reg htt_lib_f[] = {
375 {"version", luam_version},
376 {"interpret", luam_interpret},
377 {"getVar", luam_getvar},
378 {"getTransport", luam_transport_get},
379 {NULL, NULL}
380 };
381
382 static const struct luaL_Reg htt_transport_m[] = {
383 {"read", luam_transport_read},
384 {"write", luam_transport_write},
385 {"setTimeout", luam_transport_set_timeout},
386 {"getTimeout", luam_transport_get_timeout},
387 {NULL, NULL}
388 };
389
390 /**
391 * Simple lua interpreter for lua block
392 * @param worker IN callee
393 * @param parent IN caller
394 * @param ptmp IN temp pool for this function
395 * @return apr status
396 */
block_lua_interpreter(worker_t * worker,worker_t * parent,apr_pool_t * ptmp)397 static apr_status_t block_lua_interpreter(worker_t *worker, worker_t *parent,
398 apr_pool_t *ptmp) {
399 int failed;
400 int i;
401 apr_table_entry_t *e;
402 lua_reader_t *reader;
403
404 lua_wconf_t *wconf = lua_get_worker_config(worker->block);
405 lua_State *L = luaL_newstate();
406
407 luaL_openlibs(L);
408 e = (apr_table_entry_t *) apr_table_elts(wconf->params)->elts;
409 for (i = 1; i < apr_table_elts(wconf->params)->nelts; i++) {
410 const char *val = NULL;
411 char *param = store_get_copy(worker->params, ptmp, e[i].key);
412 val = worker_get_value_from_param(worker, param, ptmp);
413 lua_pushstring(L, val);
414 lua_setglobal(L, e[i].key);
415 }
416 luaopen_crypto(L);
417 lua_pushlightuserdata(L, parent);
418 lua_setfield(L, LUA_REGISTRYINDEX, "htt_parent");
419 lua_pushlightuserdata(L, worker);
420 lua_setfield(L, LUA_REGISTRYINDEX, "htt_worker");
421 luaL_newmetatable(L, "htt.transport");
422 lua_pushvalue(L, -1); /* pushes the metatable */
423 lua_setfield(L, -2, "__index"); /* metatable.__index = metatable */
424 luaL_register(L, NULL, htt_transport_m);
425 luaL_register(L, "htt", htt_lib_f);
426 lua_pop(L, -1);
427 lua_pop(L, -1);
428 reader = lua_new_lua_reader(worker, ptmp);
429 #if ( LUA_VERSION_NUM == 501 )
430 failed = (lua_load(L, lua_get_line, reader, "@client") != 0 ||
431 lua_pcall(L, 0, LUA_MULTRET, 0) != 0);
432 #elif ( LUA_VERSION_NUM == 502 )
433 failed = (lua_load(L, lua_get_line, reader, "@client", NULL) != 0 ||
434 lua_pcall(L, 0, LUA_MULTRET, 0) != 0);
435 #else
436 #error this lua version is not supported
437 #endif
438 if (failed) {
439 const char *msg = lua_tostring(L, -1);
440 if (msg == NULL) {
441 msg = "(error object is not a string)";
442 }
443 worker_log(worker, LOG_ERR, "Lua error: %s", msg);
444 lua_pop(L, 1);
445 return APR_EGENERAL;
446 }
447 e = (apr_table_entry_t *) apr_table_elts(wconf->retvars)->elts;
448 for (i = 0; i < apr_table_elts(wconf->retvars)->nelts; i++) {
449 logger_log(worker->logger, LOG_DEBUG, "param: %s; val: %s", e[i].key, e[i].val);
450 if (lua_isstring(L, i + 1)) {
451 store_set(worker->vars, store_get(worker->retvars, e[i].key), lua_tostring(L, i + 1));
452 }
453 }
454
455 lua_close(L);
456
457 return APR_SUCCESS;
458 }
459
460 /**
461 * Get variable names for in/out for mapping it to/from lua
462 * @param worker IN callee
463 * @param line IN command line
464 */
lua_set_variable_names(worker_t * worker,char * line)465 static void lua_set_variable_names(worker_t *worker, char *line) {
466 char *token;
467 char *last;
468
469 int input = 1;
470 lua_wconf_t *wconf = lua_get_worker_config(worker);
471 char *data = apr_pstrdup(worker->pbody, line);
472
473 /* Get params and returns variable names for later mapping from/to lua */
474 token = apr_strtok(data, " ", &last);
475 while (token) {
476 if (strcmp(token, ":") == 0) {
477 /* : is separator between input and output vars */
478 input = 0;
479 }
480 else {
481 if (input) {
482 apr_table_setn(wconf->params, token, token);
483 }
484 else {
485 apr_table_setn(wconf->retvars, token, token);
486 }
487 }
488 token = apr_strtok(NULL, " ", &last);
489 }
490 }
491
492 /************************************************************************
493 * Hooks
494 ***********************************************************************/
495
496 /**
497 * Start load a lua block
498 * @param global IN
499 * @param line INOUT line
500 * @return APR_SUCCESS
501 */
lua_block_start(global_t * global,char ** line)502 static apr_status_t lua_block_start(global_t *global, char **line) {
503 if (strncmp(*line, ":LUA ", 5) == 0) {
504 lua_wconf_t *wconf;
505 lua_gconf_t *gconf = lua_get_global_config(global);
506 gconf->do_read_line = 1;
507 *line += 5;
508 worker_new(&global->cur_worker, "", global, block_lua_interpreter);
509 wconf = lua_get_worker_config(global->cur_worker);
510 wconf->starting_line_nr = global->line_nr;
511 lua_set_variable_names(global->cur_worker, *line);
512 return APR_SUCCESS;
513 }
514 return APR_ENOTIMPL;
515 }
516
517 /**
518 * Read line of lua block
519 * @param global IN
520 * @param line INOUT line
521 * @return APR_SUCCESS
522 */
lua_read_line(global_t * global,char ** line)523 static apr_status_t lua_read_line(global_t *global, char **line) {
524 lua_gconf_t *gconf = lua_get_global_config(global);
525 if (gconf->do_read_line) {
526 if (*line[0] == 0) {
527 *line = apr_pstrdup(global->pool, " ");
528 }
529 }
530 return APR_SUCCESS;
531 }
532
533 /**
534 * End load a lua block
535 * @param global IN
536 * @return APR_SUCCESS
537 */
lua_block_end(global_t * global)538 static apr_status_t lua_block_end(global_t *global) {
539 lua_gconf_t *gconf = lua_get_global_config(global);
540 gconf->do_read_line = 0;
541 return APR_SUCCESS;
542 }
543
544 /************************************************************************
545 * Commands
546 ***********************************************************************/
547
548 /************************************************************************
549 * Module
550 ***********************************************************************/
lua_module_init(global_t * global)551 apr_status_t lua_module_init(global_t *global) {
552 module_command_new(global, "LUA", "_MODULE", "", "", NULL);
553 htt_hook_block_start(lua_block_start, NULL, NULL, 0);
554 htt_hook_read_line(lua_read_line, NULL, NULL, 0);
555 htt_hook_block_end(lua_block_end, NULL, NULL, 0);
556
557 return APR_SUCCESS;
558 }
559
560