/*-
* Copyright 2016 Vsevolod Stakhov
*
* 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 "lua_common.h"
LUA_FUNCTION_DEF (xmlrpc, parse_reply);
LUA_FUNCTION_DEF (xmlrpc, make_request);
static const struct luaL_reg xmlrpclib_m[] = {
LUA_INTERFACE_DEF (xmlrpc, parse_reply),
LUA_INTERFACE_DEF (xmlrpc, make_request),
{"__tostring", rspamd_lua_class_tostring},
{NULL, NULL}
};
#define msg_debug_xmlrpc(...) rspamd_conditional_debug_fast (NULL, NULL, \
rspamd_xmlrpc_log_id, "xmlrpc", "", \
G_STRFUNC, \
__VA_ARGS__)
INIT_LOG_MODULE(xmlrpc)
enum lua_xmlrpc_state {
read_method_responce = 0,
read_params = 1,
read_param = 2,
read_param_value = 3,
read_param_element = 4,
read_struct = 5,
read_struct_member_name = 6,
read_struct_member_value = 7,
read_struct_element = 8,
read_string = 9,
read_int = 10,
read_double = 11,
read_array = 12,
read_array_value = 13,
read_array_element = 14,
error_state = 99,
success_state = 100,
};
enum lua_xmlrpc_stack {
st_array = 1,
st_struct = 2,
};
struct lua_xmlrpc_ud {
enum lua_xmlrpc_state parser_state;
GQueue *st;
gint param_count;
gboolean got_text;
lua_State *L;
};
static void xmlrpc_start_element (GMarkupParseContext *context,
const gchar *name,
const gchar **attribute_names,
const gchar **attribute_values,
gpointer user_data,
GError **error);
static void xmlrpc_end_element (GMarkupParseContext *context,
const gchar *element_name,
gpointer user_data,
GError **error);
static void xmlrpc_error (GMarkupParseContext *context,
GError *error,
gpointer user_data);
static void xmlrpc_text (GMarkupParseContext *context,
const gchar *text,
gsize text_len,
gpointer user_data,
GError **error);
static GMarkupParser xmlrpc_parser = {
.start_element = xmlrpc_start_element,
.end_element = xmlrpc_end_element,
.passthrough = NULL,
.text = xmlrpc_text,
.error = xmlrpc_error,
};
static GQuark
xmlrpc_error_quark (void)
{
return g_quark_from_static_string ("xmlrpc-error-quark");
}
static void
xmlrpc_start_element (GMarkupParseContext *context,
const gchar *name,
const gchar **attribute_names,
const gchar **attribute_values,
gpointer user_data,
GError **error)
{
struct lua_xmlrpc_ud *ud = user_data;
enum lua_xmlrpc_state last_state;
last_state = ud->parser_state;
msg_debug_xmlrpc ("got start element %s on state %d", name, last_state);
switch (ud->parser_state) {
case read_method_responce:
/* Expect tag methodResponse */
if (g_ascii_strcasecmp (name, "methodResponse") == 0) {
ud->parser_state = read_params;
}
else {
/* Error state */
ud->parser_state = error_state;
}
break;
case read_params:
/* Expect tag params */
if (g_ascii_strcasecmp (name, "params") == 0) {
ud->parser_state = read_param;
/* result -> table of params indexed by int */
lua_newtable (ud->L);
}
else {
/* Error state */
ud->parser_state = error_state;
}
break;
case read_param:
/* Expect tag param */
if (g_ascii_strcasecmp (name, "param") == 0) {
ud->parser_state = read_param_value;
/* Create new param */
}
else {
/* Error state */
ud->parser_state = error_state;
}
break;
case read_param_value:
/* Expect tag value */
if (g_ascii_strcasecmp (name, "value") == 0) {
ud->parser_state = read_param_element;
}
else {
/* Error state */
ud->parser_state = error_state;
}
break;
case read_param_element:
/* Expect tag struct */
if (g_ascii_strcasecmp (name, "struct") == 0) {
ud->parser_state = read_struct;
/* Create new param of table type */
lua_newtable (ud->L);
g_queue_push_head (ud->st, GINT_TO_POINTER (st_struct));
msg_debug_xmlrpc ("push struct");
}
else if (g_ascii_strcasecmp (name, "array") == 0) {
ud->parser_state = read_array;
/* Create new param of table type */
lua_newtable (ud->L);
g_queue_push_head (ud->st, GINT_TO_POINTER (st_array));
msg_debug_xmlrpc ("push array");
}
else if (g_ascii_strcasecmp (name, "string") == 0) {
ud->parser_state = read_string;
ud->got_text = FALSE;
}
else if (g_ascii_strcasecmp (name, "int") == 0) {
ud->parser_state = read_int;
ud->got_text = FALSE;
}
else if (g_ascii_strcasecmp (name, "double") == 0) {
ud->parser_state = read_double;
ud->got_text = FALSE;
}
else {
/* Error state */
ud->parser_state = error_state;
}
break;
case read_struct:
/* Parse structure */
/* Expect tag member */
if (g_ascii_strcasecmp (name, "member") == 0) {
ud->parser_state = read_struct_member_name;
}
else {
/* Error state */
ud->parser_state = error_state;
}
break;
case read_struct_member_name:
/* Expect tag name */
if (g_ascii_strcasecmp (name, "name") == 0) {
ud->parser_state = read_struct_member_value;
}
else {
/* Error state */
ud->parser_state = error_state;
}
break;
case read_struct_member_value:
/* Accept value */
if (g_ascii_strcasecmp (name, "value") == 0) {
ud->parser_state = read_struct_element;
}
else {
/* Error state */
ud->parser_state = error_state;
}
break;
case read_struct_element:
/* Parse any values */
/* Primitives */
if (g_ascii_strcasecmp (name, "string") == 0) {
ud->parser_state = read_string;
ud->got_text = FALSE;
}
else if (g_ascii_strcasecmp (name, "int") == 0) {
ud->parser_state = read_int;
ud->got_text = FALSE;
}
else if (g_ascii_strcasecmp (name, "double") == 0) {
ud->parser_state = read_double;
ud->got_text = FALSE;
}
/* Structure */
else if (g_ascii_strcasecmp (name, "struct") == 0) {
ud->parser_state = read_struct;
/* Create new param of table type */
lua_newtable (ud->L);
g_queue_push_head (ud->st, GINT_TO_POINTER (st_struct));
msg_debug_xmlrpc ("push struct");
}
else if (g_ascii_strcasecmp (name, "array") == 0) {
ud->parser_state = read_array;
/* Create new param of table type */
lua_newtable (ud->L);
g_queue_push_head (ud->st, GINT_TO_POINTER (st_array));
msg_debug_xmlrpc ("push array");
}
else {
/* Error state */
ud->parser_state = error_state;
}
break;
case read_array:
/* Parse array */
/* Expect data */
if (g_ascii_strcasecmp (name, "data") == 0) {
ud->parser_state = read_array_value;
}
else {
/* Error state */
ud->parser_state = error_state;
}
break;
case read_array_value:
/* Accept array value */
if (g_ascii_strcasecmp (name, "value") == 0) {
ud->parser_state = read_array_element;
}
else {
/* Error state */
ud->parser_state = error_state;
}
break;
case read_array_element:
/* Parse any values */
/* Primitives */
if (g_ascii_strcasecmp (name, "string") == 0) {
ud->parser_state = read_string;
ud->got_text = FALSE;
}
else if (g_ascii_strcasecmp (name, "int") == 0) {
ud->parser_state = read_int;
ud->got_text = FALSE;
}
else if (g_ascii_strcasecmp (name, "double") == 0) {
ud->parser_state = read_double;
ud->got_text = FALSE;
}
/* Structure */
else if (g_ascii_strcasecmp (name, "struct") == 0) {
ud->parser_state = read_struct;
/* Create new param of table type */
lua_newtable (ud->L);
g_queue_push_head (ud->st, GINT_TO_POINTER (st_struct));
msg_debug_xmlrpc ("push struct");
}
else if (g_ascii_strcasecmp (name, "array") == 0) {
ud->parser_state = read_array;
/* Create new param of table type */
lua_newtable (ud->L);
g_queue_push_head (ud->st, GINT_TO_POINTER (st_array));
msg_debug_xmlrpc ("push array");
}
else {
/* Error state */
ud->parser_state = error_state;
}
break;
default:
break;
}
msg_debug_xmlrpc ("switched state on start tag %d->%d", last_state,
ud->parser_state);
if (ud->parser_state == error_state) {
g_set_error (error,
xmlrpc_error_quark (), 1, "xmlrpc parse error on state: %d, while parsing start tag: %s",
last_state, name);
}
}
static void
xmlrpc_end_element (GMarkupParseContext *context,
const gchar *name,
gpointer user_data,
GError **error)
{
struct lua_xmlrpc_ud *ud = user_data;
enum lua_xmlrpc_state last_state;
int last_queued;
last_state = ud->parser_state;
msg_debug_xmlrpc ("got end element %s on state %d", name, last_state);
switch (ud->parser_state) {
case read_method_responce:
ud->parser_state = error_state;
break;
case read_params:
/* Got methodResponse */
if (g_ascii_strcasecmp (name, "methodResponse") == 0) {
/* End processing */
ud->parser_state = success_state;
}
else {
/* Error state */
ud->parser_state = error_state;
}
break;
case read_param:
/* Got tag params */
if (g_ascii_strcasecmp (name, "params") == 0) {
ud->parser_state = read_params;
}
else {
/* Error state */
ud->parser_state = error_state;
}
break;
case read_param_value:
/* Got tag param */
if (g_ascii_strcasecmp (name, "param") == 0) {
ud->parser_state = read_param;
lua_rawseti (ud->L, -2, ++ud->param_count);
msg_debug_xmlrpc ("set param element idx: %d", ud->param_count);
}
else {
/* Error state */
ud->parser_state = error_state;
}
break;
case read_param_element:
/* Got tag value */
if (g_ascii_strcasecmp (name, "value") == 0) {
if (g_queue_get_length (ud->st) == 0) {
ud->parser_state = read_param_value;
}
else {
if (GPOINTER_TO_INT (g_queue_peek_head (ud->st)) == st_struct) {
ud->parser_state = read_struct_member_name;
}
else {
ud->parser_state = read_array_value;
}
}
}
else {
/* Error state */
ud->parser_state = error_state;
}
break;
case read_struct:
/* Got tag struct */
if (g_ascii_strcasecmp (name, "struct") == 0) {
g_assert (GPOINTER_TO_INT (g_queue_pop_head (ud->st)) == st_struct);
if (g_queue_get_length (ud->st) == 0) {
ud->parser_state = read_param_element;
}
else {
last_queued = GPOINTER_TO_INT (g_queue_peek_head (ud->st));
if (last_queued == st_struct) {
ud->parser_state = read_struct_element;
}
else {
ud->parser_state = read_array_element;
}
}
msg_debug_xmlrpc ("pop struct");
}
else {
/* Error state */
ud->parser_state = error_state;
}
break;
case read_struct_member_name:
/* Got tag member */
if (g_ascii_strcasecmp (name, "member") == 0) {
ud->parser_state = read_struct;
/* Set table */
msg_debug_xmlrpc ("set struct element idx: %s",
lua_tostring (ud->L, -2));
lua_settable (ud->L, -3);
}
else {
/* Error state */
ud->parser_state = error_state;
}
break;
case read_struct_member_value:
/* Got tag name */
if (g_ascii_strcasecmp (name, "name") == 0) {
ud->parser_state = read_struct_member_value;
}
else {
/* Error state */
ud->parser_state = error_state;
}
break;
case read_struct_element:
/* Got tag value */
if (g_ascii_strcasecmp (name, "value") == 0) {
ud->parser_state = read_struct_member_name;
}
else {
/* Error state */
ud->parser_state = error_state;
}
break;
case read_string:
case read_int:
case read_double:
/* Parse any values */
/* Handle empty tags */
if (!ud->got_text) {
lua_pushnil (ud->L);
}
else {
ud->got_text = FALSE;
}
/* Primitives */
if (g_ascii_strcasecmp (name, "string") == 0 ||
g_ascii_strcasecmp (name, "int") == 0 ||
g_ascii_strcasecmp (name, "double") == 0) {
if (GPOINTER_TO_INT (g_queue_peek_head (ud->st)) == st_struct) {
ud->parser_state = read_struct_element;
}
else {
ud->parser_state = read_array_element;
}
}
else {
/* Error state */
ud->parser_state = error_state;
}
break;
case read_array:
/* Got tag array */
if (g_ascii_strcasecmp (name, "array") == 0) {
g_assert (GPOINTER_TO_INT (g_queue_pop_head (ud->st)) == st_array);
if (g_queue_get_length (ud->st) == 0) {
ud->parser_state = read_param_element;
}
else {
last_queued = GPOINTER_TO_INT (g_queue_peek_head (ud->st));
if (last_queued == st_struct) {
ud->parser_state = read_struct_element;
}
else {
ud->parser_state = read_array_element;
}
}
msg_debug_xmlrpc ("pop array");
}
else {
/* Error state */
ud->parser_state = error_state;
}
break;
case read_array_value:
/* Got tag data */
if (g_ascii_strcasecmp (name, "data") == 0) {
ud->parser_state = read_array;
}
else {
/* Error state */
ud->parser_state = error_state;
}
break;
case read_array_element:
/* Got tag value */
if (g_ascii_strcasecmp (name, "value") == 0) {
guint tbl_len = rspamd_lua_table_size (ud->L, -2);
lua_rawseti (ud->L, -2, tbl_len + 1);
msg_debug_xmlrpc ("set array element idx: %d", tbl_len + 1);
ud->parser_state = read_array_value;
}
else {
/* Error state */
ud->parser_state = error_state;
}
break;
default:
break;
}
msg_debug_xmlrpc ("switched state on end tag %d->%d",
last_state, ud->parser_state);
if (ud->parser_state == error_state) {
g_set_error (error,
xmlrpc_error_quark (), 1, "xmlrpc parse error on state: %d, while parsing end tag: %s",
last_state, name);
}
}
static void
xmlrpc_text (GMarkupParseContext *context,
const gchar *text,
gsize text_len,
gpointer user_data,
GError **error)
{
struct lua_xmlrpc_ud *ud = user_data;
gulong num;
gdouble dnum;
/* Strip line */
while (text_len > 0 && g_ascii_isspace (*text)) {
text++;
text_len--;
}
while (text_len > 0 && g_ascii_isspace (text[text_len - 1])) {
text_len--;
}
if (text_len > 0) {
msg_debug_xmlrpc ("got data on state %d", ud->parser_state);
switch (ud->parser_state) {
case read_struct_member_value:
/* Push key */
lua_pushlstring (ud->L, text, text_len);
break;
case read_string:
/* Push string value */
lua_pushlstring (ud->L, text, text_len);
break;
case read_int:
/* Push integer value */
rspamd_strtoul (text, text_len, &num);
lua_pushinteger (ud->L, num);
break;
case read_double:
/* Push integer value */
dnum = strtod (text, NULL);
lua_pushnumber (ud->L, dnum);
break;
default:
break;
}
ud->got_text = TRUE;
}
}
static void
xmlrpc_error (GMarkupParseContext *context, GError *error, gpointer user_data)
{
msg_err ("xmlrpc parser error: %s", error->message);
}
static gint
lua_xmlrpc_parse_reply (lua_State *L)
{
LUA_TRACE_POINT;
const gchar *data;
GMarkupParseContext *ctx;
GError *err = NULL;
struct lua_xmlrpc_ud ud;
gsize s;
gboolean res;
data = luaL_checklstring (L, 1, &s);
if (data != NULL) {
ud.L = L;
ud.parser_state = read_method_responce;
ud.param_count = 0;
ud.st = g_queue_new ();
ctx = g_markup_parse_context_new (&xmlrpc_parser,
G_MARKUP_TREAT_CDATA_AS_TEXT, &ud, NULL);
res = g_markup_parse_context_parse (ctx, data, s, &err);
g_markup_parse_context_free (ctx);
if (!res) {
lua_pushnil (L);
}
}
else {
lua_pushnil (L);
}
/* Return table or nil */
return 1;
}
static gint
lua_xmlrpc_parse_table (lua_State *L,
gint pos,
gchar *databuf,
gint pr,
gsize size)
{
gint r = pr, num;
double dnum;
r += rspamd_snprintf (databuf + r, size - r, "");
lua_pushnil (L); /* first key */
while (lua_next (L, pos) != 0) {
/* uses 'key' (at index -2) and 'value' (at index -1) */
if (lua_type (L, -2) != LUA_TSTRING) {
/* Ignore non sting keys */
lua_pop (L, 1);
continue;
}
r += rspamd_snprintf (databuf + r,
size - r,
"%s",
lua_tostring (L, -2));
switch (lua_type (L, -1)) {
case LUA_TNUMBER:
num = lua_tointeger (L, -1);
dnum = lua_tonumber (L, -1);
/* Try to avoid conversion errors */
if (dnum != (double)num) {
r += rspamd_snprintf (databuf + r,
sizeof (databuf) - r,
"%f",
dnum);
}
else {
r += rspamd_snprintf (databuf + r,
sizeof (databuf) - r,
"%d",
num);
}
break;
case LUA_TBOOLEAN:
r += rspamd_snprintf (databuf + r,
size - r,
"%d",
lua_toboolean (L, -1) ? 1 : 0);
break;
case LUA_TSTRING:
r += rspamd_snprintf (databuf + r, size - r, "%s",
lua_tostring (L, -1));
break;
case LUA_TTABLE:
/* Recursive call */
r += lua_xmlrpc_parse_table (L, -1, databuf + r, r, size);
break;
}
r += rspamd_snprintf (databuf + r, size - r, "");
/* removes 'value'; keeps 'key' for next iteration */
lua_pop (L, 1);
}
r += rspamd_snprintf (databuf + r, size - r, "");
return r - pr;
}
/*
* Internal limitation: xmlrpc request must NOT be more than
* BUFSIZ * 2 (16384 bytes)
*/
static gint
lua_xmlrpc_make_request (lua_State *L)
{
LUA_TRACE_POINT;
gchar databuf[BUFSIZ * 2];
const gchar *func;
gint r, top, i, num;
double dnum;
func = luaL_checkstring (L, 1);
if (func) {
r = rspamd_snprintf (databuf, sizeof(databuf),
""
"%s",
func);
/* Extract arguments */
top = lua_gettop (L);
/* Get additional options */
for (i = 2; i <= top; i++) {
r += rspamd_snprintf (databuf + r,
sizeof (databuf) - r,
"");
switch (lua_type (L, i)) {
case LUA_TNUMBER:
num = lua_tointeger (L, i);
dnum = lua_tonumber (L, i);
/* Try to avoid conversion errors */
if (dnum != (double)num) {
r += rspamd_snprintf (databuf + r,
sizeof (databuf) - r,
"%f",
dnum);
}
else {
r += rspamd_snprintf (databuf + r,
sizeof (databuf) - r,
"%d",
num);
}
break;
case LUA_TBOOLEAN:
r += rspamd_snprintf (databuf + r,
sizeof (databuf) - r,
"%d",
lua_toboolean (L, i) ? 1 : 0);
break;
case LUA_TSTRING:
r += rspamd_snprintf (databuf + r,
sizeof (databuf) - r,
"%s",
lua_tostring (L, i));
break;
case LUA_TTABLE:
r +=
lua_xmlrpc_parse_table (L, i, databuf, r, sizeof (databuf));
break;
}
r += rspamd_snprintf (databuf + r,
sizeof (databuf) - r,
"");
}
r += rspamd_snprintf (databuf + r,
sizeof (databuf) - r,
"");
lua_pushlstring (L, databuf, r);
}
else {
lua_pushnil (L);
}
return 1;
}
static gint
lua_load_xmlrpc (lua_State * L)
{
lua_newtable (L);
luaL_register (L, NULL, xmlrpclib_m);
return 1;
}
void
luaopen_xmlrpc (lua_State * L)
{
rspamd_lua_add_preload (L, "rspamd_xmlrpc", lua_load_xmlrpc);
}