/*-
* 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}
};
struct lua_xmlrpc_ud {
gint parser_state;
gint depth;
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;
int last_state;
last_state = ud->parser_state;
switch (ud->parser_state) {
case 0:
/* Expect tag methodResponse */
if (g_ascii_strcasecmp (name, "methodResponse") == 0) {
ud->parser_state = 1;
}
else {
/* Error state */
ud->parser_state = 99;
}
break;
case 1:
/* Expect tag params */
if (g_ascii_strcasecmp (name, "params") == 0) {
ud->parser_state = 2;
/* result -> table of params indexed by int */
lua_newtable (ud->L);
}
else {
/* Error state */
ud->parser_state = 99;
}
break;
case 2:
/* Expect tag param */
if (g_ascii_strcasecmp (name, "param") == 0) {
ud->parser_state = 3;
/* Create new param */
}
else {
/* Error state */
ud->parser_state = 99;
}
break;
case 3:
/* Expect tag value */
if (g_ascii_strcasecmp (name, "value") == 0) {
ud->parser_state = 4;
}
else {
/* Error state */
ud->parser_state = 99;
}
break;
case 4:
/* Expect tag struct */
if (g_ascii_strcasecmp (name, "struct") == 0) {
ud->parser_state = 5;
/* Create new param of table type */
lua_newtable (ud->L);
ud->depth++;
}
else if (g_ascii_strcasecmp (name, "string") == 0) {
ud->parser_state = 11;
ud->got_text = FALSE;
}
else if (g_ascii_strcasecmp (name, "int") == 0) {
ud->parser_state = 12;
ud->got_text = FALSE;
}
else if (g_ascii_strcasecmp (name, "double") == 0) {
ud->parser_state = 13;
ud->got_text = FALSE;
}
else {
/* Error state */
ud->parser_state = 99;
}
break;
case 5:
/* Parse structure */
/* Expect tag member */
if (g_ascii_strcasecmp (name, "member") == 0) {
ud->parser_state = 6;
}
else {
/* Error state */
ud->parser_state = 99;
}
break;
case 6:
/* Expect tag name */
if (g_ascii_strcasecmp (name, "name") == 0) {
ud->parser_state = 7;
}
else {
/* Error state */
ud->parser_state = 99;
}
break;
case 7:
/* Accept value */
if (g_ascii_strcasecmp (name, "value") == 0) {
ud->parser_state = 8;
}
else {
/* Error state */
ud->parser_state = 99;
}
break;
case 8:
/* Parse any values */
/* Primitives */
if (g_ascii_strcasecmp (name, "string") == 0) {
ud->parser_state = 11;
ud->got_text = FALSE;
}
else if (g_ascii_strcasecmp (name, "int") == 0) {
ud->parser_state = 12;
ud->got_text = FALSE;
}
else if (g_ascii_strcasecmp (name, "double") == 0) {
ud->parser_state = 13;
ud->got_text = FALSE;
}
/* Structure */
else if (g_ascii_strcasecmp (name, "struct") == 0) {
ud->parser_state = 5;
/* Create new param of table type */
lua_newtable (ud->L);
ud->depth++;
}
else {
/* Error state */
ud->parser_state = 99;
}
break;
}
if (ud->parser_state == 99) {
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;
int last_state;
last_state = ud->parser_state;
switch (ud->parser_state) {
case 0:
ud->parser_state = 99;
break;
case 1:
/* Got methodResponse */
if (g_ascii_strcasecmp (name, "methodResponse") == 0) {
/* End processing */
ud->parser_state = 100;
}
else {
/* Error state */
ud->parser_state = 99;
}
break;
case 2:
/* Got tag params */
if (g_ascii_strcasecmp (name, "params") == 0) {
ud->parser_state = 1;
}
else {
/* Error state */
ud->parser_state = 99;
}
break;
case 3:
/* Got tag param */
if (g_ascii_strcasecmp (name, "param") == 0) {
ud->parser_state = 2;
lua_rawseti (ud->L, -2, ++ud->param_count);
}
else {
/* Error state */
ud->parser_state = 99;
}
break;
case 4:
/* Got tag value */
if (g_ascii_strcasecmp (name, "value") == 0) {
if (ud->depth == 0) {
ud->parser_state = 3;
}
else {
/* Parse other members */
ud->parser_state = 6;
}
}
else {
/* Error state */
ud->parser_state = 99;
}
break;
case 5:
/* Got tag struct */
if (g_ascii_strcasecmp (name, "struct") == 0) {
ud->parser_state = 4;
ud->depth--;
}
else {
/* Error state */
ud->parser_state = 99;
}
break;
case 6:
/* Got tag member */
if (g_ascii_strcasecmp (name, "member") == 0) {
ud->parser_state = 5;
/* Set table */
lua_settable (ud->L, -3);
}
else {
/* Error state */
ud->parser_state = 99;
}
break;
case 7:
/* Got tag name */
if (g_ascii_strcasecmp (name, "name") == 0) {
ud->parser_state = 7;
}
else {
/* Error state */
ud->parser_state = 99;
}
break;
case 8:
/* Got tag value */
if (g_ascii_strcasecmp (name, "value") == 0) {
ud->parser_state = 6;
}
else {
/* Error state */
ud->parser_state = 99;
}
break;
case 11:
case 12:
case 13:
/* 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) {
ud->parser_state = 8;
}
else if (g_ascii_strcasecmp (name, "int") == 0) {
ud->parser_state = 8;
}
else if (g_ascii_strcasecmp (name, "double") == 0) {
ud->parser_state = 8;
}
else {
/* Error state */
ud->parser_state = 99;
}
break;
}
if (ud->parser_state == 99) {
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;
gint 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) {
switch (ud->parser_state) {
case 7:
/* Push key */
lua_pushlstring (ud->L, text, text_len);
break;
case 11:
/* Push string value */
lua_pushlstring (ud->L, text, text_len);
break;
case 12:
/* Push integer value */
num = strtoul (text, NULL, 10);
lua_pushinteger (ud->L, num);
break;
case 13:
/* Push integer value */
dnum = strtod (text, NULL);
lua_pushnumber (ud->L, dnum);
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)
{
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 = 0;
ud.depth = 0;
ud.param_count = 0;
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)
{
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);
}