From b5ab27845bffbc133bb1c5b3afbdce34ac1ec32f Mon Sep 17 00:00:00 2001 From: Vsevolod Stakhov Date: Thu, 6 Aug 2015 16:56:53 +0100 Subject: [PATCH] Add universal function to parse lua tables. --- src/lua/lua_common.c | 232 +++++++++++++++++++++++++++++++++++++++++++ src/lua/lua_common.h | 20 ++++ 2 files changed, 252 insertions(+) diff --git a/src/lua/lua_common.c b/src/lua/lua_common.c index 59a78d873..a45f3871d 100644 --- a/src/lua/lua_common.c +++ b/src/lua/lua_common.c @@ -32,6 +32,11 @@ const luaL_reg null_reg[] = { {NULL, NULL} }; +static GQuark +lua_error_quark (void) +{ + return g_quark_from_static_string ("lua-routines"); +} /* Util functions */ /** @@ -561,3 +566,230 @@ rspamd_lua_add_preload (lua_State *L, const gchar *name, lua_CFunction func) lua_setfield (L, -2, name); lua_pop (L, 1); } + + +gboolean +rspamd_lua_parse_table_arguments (lua_State *L, gint pos, + GError **err, const gchar *extraction_pattern, ...) +{ + const gchar *p, *key = NULL, *end, *cls; + va_list ap; + gboolean required = FALSE, failed = FALSE; + gchar classbuf[128]; + enum { + read_key = 0, + read_arg, + read_class_start, + read_class, + read_semicolon + } state = read_key; + gsize keylen = 0, *valuelen, clslen; + + if (pos < 0) { + /* Get absolute pos */ + pos = lua_gettop (L) + pos + 1; + } + + g_assert (extraction_pattern != NULL); + g_assert (lua_type (L, pos) == LUA_TTABLE); + + p = extraction_pattern; + end = p + strlen (extraction_pattern); + + va_start (ap, extraction_pattern); + + while (p <= end) { + switch (state) { + case read_key: + if (*p == '=') { + if (key == NULL) { + g_set_error (err, lua_error_quark (), 1, "cannot read key"); + va_end (ap); + + return FALSE; + } + + state = read_arg; + keylen = p - key; + } + else if (*p == '*') { + required = TRUE; + } + else { + key = p; + } + p ++; + break; + case read_arg: + g_assert (keylen != 0); + lua_pushlstring (L, key, keylen); + lua_gettable (L, pos); + + switch (g_ascii_toupper (*p)) { + case 'S': + if (lua_type (L, -1) == LUA_TSTRING) { + *(va_arg (ap, const gchar **)) = lua_tostring (L, -1); + } + else { + failed = TRUE; + *(va_arg (ap, const gchar **)) = NULL; + } + lua_pop (L, 1); + break; + + case 'I': + if (lua_type (L, -1) == LUA_TNUMBER) { + *(va_arg (ap, gint64 *)) = lua_tonumber (L, -1); + } + else { + failed = TRUE; + *(va_arg (ap, gint64 *)) = 0; + } + lua_pop (L, 1); + break; + + case 'B': + if (lua_type (L, -1) == LUA_TBOOLEAN) { + *(va_arg (ap, gboolean *)) = lua_toboolean (L, -1); + } + else { + failed = TRUE; + *(va_arg (ap, gboolean *)) = 0; + } + lua_pop (L, 1); + break; + + case 'N': + if (lua_type (L, -1) == LUA_TNUMBER) { + *(va_arg (ap, gdouble *)) = lua_tonumber (L, -1); + } + else { + failed = TRUE; + *(va_arg (ap, gdouble *)) = 0; + } + lua_pop (L, 1); + break; + + case 'V': + valuelen = va_arg (ap, gsize *); + if (lua_type (L, -1) == LUA_TSTRING) { + *(va_arg (ap, const gchar **)) = lua_tolstring (L, -1, + valuelen); + } + else { + failed = TRUE; + *(va_arg (ap, const char **)) = NULL; + *valuelen = 0; + } + lua_pop (L, 1); + break; + case 'U': + if (lua_type (L, -1) != LUA_TUSERDATA) { + failed = TRUE; + } + state = read_class_start; + clslen = 0; + cls = NULL; + p ++; + continue; + } + + if (failed && required) { + g_set_error (err, lua_error_quark (), 2, "required parameter " + "%.*s is missing", (gint)keylen, key); + va_end (ap); + + return FALSE; + } + + /* Reset read params */ + state = read_semicolon; + failed = FALSE; + required = FALSE; + keylen = 0; + key = NULL; + p ++; + break; + + case read_class_start: + if (*p == '{') { + cls = p + 1; + state = read_class; + } + else { + lua_pop (L, 1); + g_set_error (err, lua_error_quark (), 2, "missing classname for " + "%.*s", (gint)keylen, key); + va_end (ap); + + return FALSE; + } + p ++; + break; + + case read_class: + if (*p == '}') { + clslen = p - cls; + } + if (clslen == 0) { + lua_pop (L, 1); + g_set_error (err, lua_error_quark (), 2, "empty classname for " + "%*.s", (gint)keylen, key); + va_end (ap); + + return FALSE; + } + + rspamd_snprintf (classbuf, sizeof (classbuf), "{%*s}", + (gint)clslen, cls); + + if (!failed && rspamd_lua_check_class (L, -1, classbuf)) { + *(va_arg (ap, gpointer *)) = lua_touserdata (L, -1); + } + else { + *(va_arg (ap, gpointer *)) = NULL; + failed = TRUE; + } + lua_pop (L, 1); + + if (failed && required) { + g_set_error (err, lua_error_quark (), 2, "required parameter " + "%.*s is missing", (gint)keylen, key); + va_end (ap); + + return FALSE; + } + + /* Reset read params */ + state = read_semicolon; + failed = FALSE; + required = FALSE; + keylen = 0; + key = NULL; + p ++; + break; + + case read_semicolon: + if (*p == ':') { + state = read_key; + key = NULL; + keylen = 0; + failed = FALSE; + } + else { + g_set_error (err, lua_error_quark (), 2, "bad format string: %s", + extraction_pattern); + va_end (ap); + + return FALSE; + } + + p++; + break; + } + } + + va_end (ap); + + return TRUE; +} diff --git a/src/lua/lua_common.h b/src/lua/lua_common.h index 152321373..5321d0409 100644 --- a/src/lua/lua_common.h +++ b/src/lua/lua_common.h @@ -260,6 +260,26 @@ void rspamd_lua_set_path (lua_State *L, struct rspamd_config *cfg); struct memory_pool_s * rspamd_lua_check_mempool (lua_State * L, gint pos); struct rspamd_config * lua_check_config (lua_State * L, gint pos); +/** + * Extract an arguments from lua table according to format string. Supported arguments are: + * key=[*]S|I|N|B|V|U{a-z};[key=...] + * - S - const char * + * - I - gint64_t + * - N - double + * - B - boolean + * - V - size_t + const char * + * - U{classname} - userdata of the following class (stored in gpointer) + * + * If any of format string is prefixed with `*` then it is treated as required argument + * @param L lua state + * @param pos at which pos start extraction + * @param err error pointer + * @param extraction_pattern static pattern + * @return TRUE if a table has been parsed + */ +gboolean rspamd_lua_parse_table_arguments (lua_State *L, gint pos, + GError **err, const gchar *extraction_pattern, ...); + #endif /* WITH_LUA */ #endif /* RSPAMD_LUA_H */ -- 2.39.5