2
0
Эх сурвалжийг харах

xhttp_rpc: new module providing a web interface for RPC management interface

Ovidiu Sas 14 жил өмнө
parent
commit
96c2f8d459

+ 14 - 0
modules/xhttp_rpc/Makefile

@@ -0,0 +1,14 @@
+# $Id$
+#
+# 
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+auto_gen=
+NAME=xhttp_rpc.so
+DEFS +=
+LIBS +=
+
+DEFS+=-DOPENSER_MOD_INTERFACE
+
+include ../../Makefile.modules

+ 165 - 0
modules/xhttp_rpc/README

@@ -0,0 +1,165 @@
+xHTTP_RPC Module
+
+Ovidiu Sas
+
+   <[email protected]>
+
+Edited by
+
+Ovidiu Sas
+
+   <[email protected]>
+
+   Copyright © 2011 VoIPEmbedded Inc.
+     __________________________________________________________________
+
+   Table of Contents
+
+   1. Admin Guide
+
+        1. Overview
+
+              1.1. Limitations
+
+        2. Dependencies
+
+              2.1. Kamailio Modules
+              2.2. External Libraries or Applications
+
+        3. Parameters
+
+              3.1. xhttp_rpc_root (str)
+              3.2. xhttp_rpc_buf_size (str)
+
+        4. Functions
+
+              4.1. dispatch_xhttp_rpc()
+
+   List of Examples
+
+   1.1. Set xhttp_rpc_root parameter
+   1.2. Set xhttp_rpc_buf_size parameter
+   1.3. dispatch_xhttp_rpc usage
+
+Chapter 1. Admin Guide
+
+   Table of Contents
+
+   1. Overview
+
+        1.1. Limitations
+
+   2. Dependencies
+
+        2.1. Kamailio Modules
+        2.2. External Libraries or Applications
+
+   3. Parameters
+
+        3.1. xhttp_rpc_root (str)
+        3.2. xhttp_rpc_buf_size (str)
+
+   4. Functions
+
+        4.1. dispatch_xhttp_rpc()
+
+1. Overview
+
+   1.1. Limitations
+
+   This module provides an HTTP transport layer implementation for the RPC
+   management interface in a human easy readable format.
+
+   The xHTTP_RPC module uses the xHTTP module to handle HTTP requests.
+   Read the documentation of the xHTTP module for more details.
+
+1.1. Limitations
+
+   This module does not implement asynchronous RPC commands. It is
+   unlikely that asynchronous RPC commands will be executed from an RPC
+   web interface.
+
+   This module does not accept parameters embedded in a structure (see RPC
+   documentation for more info about how parameters can be passed to RPC).
+
+   At startup, all RPC commands are sorted and grouped based on their
+   format. The expected format is [group].[subcommand]. The initial
+   xhttp_rpc webpage displays all the retrieved groups. All RPC commands
+   are disponible as submenus of each [group]. If an RPC command is not in
+   the expected format, it will be dropped from the initial xhttp_rpc home
+   page menu.
+
+2. Dependencies
+
+   2.1. Kamailio Modules
+   2.2. External Libraries or Applications
+
+2.1. Kamailio Modules
+
+   The following modules must be loaded before this module:
+     * xhttp - xHTTP.
+
+2.2. External Libraries or Applications
+
+   The following libraries or applications must be installed before
+   running Kamailio with this module loaded:
+     * None
+
+3. Parameters
+
+   3.1. xhttp_rpc_root (str)
+   3.2. xhttp_rpc_buf_size (str)
+
+3.1. xhttp_rpc_root (str)
+
+   It specifies the root path for RPC http requests. The link to the RPC
+   web interface must be constructed using the following patern:
+   http://[server_IP]:[tcp_port]/[xhttp_rpc_root]
+
+   Default value is "rpc".
+
+   Example 1.1. Set xhttp_rpc_root parameter
+...
+modparam("xhttp_rpc", "xhttp_rpc_root", "http_rpc")
+...
+
+3.2. xhttp_rpc_buf_size (str)
+
+   It specifies the maximum length of the buffer used to write in the RPC
+   reply information in order to build the html response.
+
+   Default value is 0 (auto set to 1/3 of the size of the configured pkg
+   mem).
+
+   Example 1.2. Set xhttp_rpc_buf_size parameter
+...
+modparam("xhttp", "xhttp_rpc_buf_size", )
+...
+
+4. Functions
+
+   4.1. dispatch_xhttp_rpc()
+
+4.1.  dispatch_xhttp_rpc()
+
+   Handle the HTTP request and generate back a response.
+
+   Example 1.3. dispatch_xhttp_rpc usage
+...
+tcp_accept_no_cl=yes
+...
+loadmodule "sl.so"
+loadmodule "xhttp.so"
+loadmodule "xhttp_rpc.so"
+...
+modparam("xhttp_rpc", "xhttp_rpc_root", "http_rpc")
+...
+event_route[xhttp:request] {
+        $var(xhttp_rpc_root) = $(hu{s.substr,0,9});
+        if ($var(xhttp_rpc_root) == "/http_rpc")
+                dispatch_xhttp_rpc();
+        else
+                xhttp_reply("200", "OK", "text/html",
+                        "<html><body>Wrong URL $hu</body></html>");
+}
+...

+ 4 - 0
modules/xhttp_rpc/doc/Makefile

@@ -0,0 +1,4 @@
+docs = xhttp_rpc.xml
+
+docbook_dir = ../../../docbook
+include $(docbook_dir)/Makefile.module

+ 37 - 0
modules/xhttp_rpc/doc/xhttp_rpc.xml

@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../docbook/entities.xml">
+%docentities;
+
+]>
+
+<book xmlns:xi="http://www.w3.org/2001/XInclude">
+    <bookinfo>
+	<title>xHTTP_RPC Module</title>
+	<productname class="trade">sip-router.org</productname>
+	<authorgroup>
+	    <author>
+		<firstname>Ovidiu</firstname>
+		<surname>Sas</surname>
+		<email>[email protected]</email>
+	    </author>
+	    <editor>
+		<firstname>Ovidiu</firstname>
+		<surname>Sas</surname>
+		<email>[email protected]</email>
+	    </editor>
+	</authorgroup>
+	<copyright>
+	    <year>2011</year>
+	    <holder><ulink url='http://www.voipembedded.com'>VoIPEmbedded Inc.</ulink></holder>
+	</copyright>
+    </bookinfo>
+    <toc></toc>
+    
+    <xi:include href="xhttp_rpc_admin.xml"/>
+    
+    
+</book>

+ 162 - 0
modules/xhttp_rpc/doc/xhttp_rpc_admin.xml

@@ -0,0 +1,162 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../docbook/entities.xml">
+%docentities;
+
+]>
+<!-- Module User's Guide -->
+
+<chapter>
+	
+	<title>&adminguide;</title>
+	
+	<section>
+	<title>Overview</title>
+	<para>
+		This module provides an HTTP transport layer implementation for
+		the RPC management interface in a human easy readable format.
+	<para>
+	</para>
+		The xHTTP_RPC module uses the xHTTP module to handle HTTP requests.
+		Read the documentation of the xHTTP module for more details.
+	</para>
+
+	<section>
+	<title>Limitations</title>
+	<para>
+		This module does not implement asynchronous RPC commands.
+		It is unlikely that asynchronous RPC commands will be executed
+		from an RPC web interface.
+	</para>
+	<para>
+		This module does not accept parameters embedded in a structure
+		(see RPC documentation for more info about how parameters can be
+		passed to RPC).
+	</para>
+	<para>
+		At startup, all RPC commands are sorted and grouped based on
+		their format.  The expected format is [group].[subcommand].
+		The initial xhttp_rpc webpage displays all the retrieved groups.
+		All RPC commands are disponible as submenus of each [group].
+		If an RPC command is not in the expected format, it will be
+		dropped from the initial xhttp_rpc home page menu.
+	</para>
+	</section>
+	</section>
+
+	<section>
+	<title>Dependencies</title>
+	<section>
+		<title>&kamailio; Modules</title>
+		<para>
+		The following modules must be loaded before this module:
+			<itemizedlist>
+			<listitem>
+			<para>
+				<emphasis>xhttp</emphasis> - xHTTP.
+			</para>
+			</listitem>
+			</itemizedlist>
+		</para>
+	</section>
+	<section>
+		<title>External Libraries or Applications</title>
+		<para>
+		The following libraries or applications must be installed before running
+		&kamailio; with this module loaded:
+			<itemizedlist>
+			<listitem>
+			<para>
+				<emphasis>None</emphasis>
+			</para>
+			</listitem>
+			</itemizedlist>
+		</para>
+	</section>
+	</section>
+	<section>
+	<title>Parameters</title>
+	<section>
+		<title><varname>xhttp_rpc_root</varname> (str)</title>
+		<para>
+			It specifies the root path for RPC http requests.
+			The link to the RPC web interface must be constructed
+			using the following patern:
+			http://[server_IP]:[tcp_port]/[xhttp_rpc_root] 
+		</para>
+		<para>
+		<emphasis>
+			Default value is "rpc".
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>xhttp_rpc_root</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("xhttp_rpc", "xhttp_rpc_root", "http_rpc")
+...
+</programlisting>
+		</example>
+	</section>
+	<section>
+		<title><varname>xhttp_rpc_buf_size</varname> (str)</title>
+		<para>
+			It specifies the maximum length of the buffer used
+			to write in the RPC reply information in order to
+			build the html response.
+		</para>
+		<para>
+		<emphasis>
+			Default value is 0 (auto set to 1/3 of the size of the configured pkg mem).
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>xhttp_rpc_buf_size</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("xhttp", "xhttp_rpc_buf_size", )
+...
+</programlisting>
+		</example>
+	</section>
+	</section>
+
+	<section>
+	<title>Functions</title>
+ 	<section>
+	    <title>
+		<function moreinfo="none">dispatch_xhttp_rpc()</function>
+	    </title>
+	    <para>
+		Handle the HTTP request and generate back a response.
+	    </para>
+		<example>
+		<title><function>dispatch_xhttp_rpc</function> usage</title>
+		<programlisting format="linespecific">
+...
+tcp_accept_no_cl=yes
+...
+loadmodule "sl.so"
+loadmodule "xhttp.so"
+loadmodule "xhttp_rpc.so"
+...
+modparam("xhttp_rpc", "xhttp_rpc_root", "http_rpc")
+...
+event_route[xhttp:request] {
+	$var(xhttp_rpc_root) = $(hu{s.substr,0,9});
+	if ($var(xhttp_rpc_root) == "/http_rpc")
+		dispatch_xhttp_rpc();
+	else
+		xhttp_reply("200", "OK", "text/html",
+        		"&lt;html&gt;&lt;body&gt;Wrong URL $hu&lt;/body&gt;&lt;/html&gt;");
+}
+...
+</programlisting>
+	    </example>
+	</section>
+	</section>
+</chapter>
+

+ 816 - 0
modules/xhttp_rpc/xhttp_rpc.c

@@ -0,0 +1,816 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2011 VoIP Embedded, Inc.
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * 2011-11-11  initial version (osas)
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#include "../../ver.h"
+#include "../../trim.h"
+#include "../../sr_module.h"
+#include "../../nonsip_hooks.h"
+#include "../../modules/xhttp/api.h"
+#include "xhttp_rpc.h"
+#include "xhttp_rpc_fnc.h"
+
+/** @addtogroup xhttp_rpc
+ * @ingroup modules
+ * @{
+ *
+ * <h1>Overview of Operation</h1>
+ * This module provides a web interface for RPC management interface.
+ * It is built on top of the xhttp API module.
+ */
+
+/** @file
+ *
+ * This is the main file of xhttp_rpc module which contains all the functions
+ * related to http processing, as well as the module interface.
+ */
+
+MODULE_VERSION
+
+str XHTTP_RPC_REASON_OK = str_init("OK");
+str XHTTP_RPC_CONTENT_TYPE_TEXT_HTML = str_init("text/html");
+
+
+xhttp_rpc_mod_cmds_t *xhttp_rpc_mod_cmds = NULL;
+int xhttp_rpc_mod_cmds_size = 0;
+
+/* FIXME: this should be initialized in ../../ver.c */
+int full_version_len;
+int ver_name_len;
+
+static int mod_init(void);
+static int child_init(int rank);
+static int xhttp_rpc_dispatch(sip_msg_t* msg, char* s1, char* s2);
+
+
+/** The context of the xhttp_rpc request being processed.
+ *
+ * This is a global variable that records the context of the xhttp_rpc request
+ * being currently processed.
+ * @sa rpc_ctx
+ */
+static rpc_ctx_t ctx;
+
+static xhttp_api_t xhttp_api;
+
+/** Pointers to the functions that implement the RPC interface
+ * of xhttp_rpc module
+ */
+static rpc_t func_param;
+
+str xhttp_rpc_root = str_init("rpc");
+int buf_size = 0;
+char error_buf[ERROR_REASON_BUF_LEN];
+
+static cmd_export_t cmds[] = {
+	{"dispatch_xhttp_rpc",(cmd_function)xhttp_rpc_dispatch,0,0,0,REQUEST_ROUTE},
+	{0, 0, 0, 0, 0, 0}
+};
+
+static param_export_t params[] = {
+	{"xhttp_rpc_root",	STR_PARAM,	&xhttp_rpc_root.s},
+	{"xhttp_rpc_buf_size",	INT_PARAM,	&buf_size},
+	{0, 0, 0}
+};
+
+/** module exports */
+struct module_exports exports= {
+	"xhttp_rpc",
+	DEFAULT_DLFLAGS, /* dlopen flags */
+	cmds,
+	params,
+	0,		/* exported statistics */
+	0,		/* exported MI functions */
+	0,		/* exported pseudo-variables */
+	0,		/* extra processes */
+	mod_init,	/* module initialization function */
+	0,
+	0,
+	child_init	/* per-child init function */
+};
+
+
+/** Implementation of rpc_fault function required by the management API.
+ *
+ * This function will be called whenever a management function
+ * indicates that an error ocurred while it was processing the request. The
+ * function takes the reply code and reason phrase as parameters, these will
+ * be put in the body of the reply.
+ *
+ * @param ctx A pointer to the context structure of the request being
+ *            processed.
+ * @param code Reason code.
+ * @param fmt Formatting string used to build the reason phrase.
+ */
+static void rpc_fault(rpc_ctx_t* ctx, int code, char* fmt, ...)
+{
+	va_list ap;
+	struct xhttp_rpc_reply *reply = &ctx->reply;
+
+	reply->code = code;
+	va_start(ap, fmt);
+	vsnprintf(error_buf, ERROR_REASON_BUF_LEN, fmt, ap);
+	va_end(ap);
+	reply->reason.len = strlen(error_buf);
+	reply->reason.s = error_buf;
+	/* reset body so we can print the error */
+	reply->body.len = 0;
+
+	return;
+}
+
+
+/**
+ */
+static void free_data_struct(struct rpc_data_struct *rpc_d)
+{
+	struct rpc_data_struct *ds;
+
+	if (!rpc_d) {
+		LM_ERR("Atempting to free NULL rpc_data_struct\n");
+		return;
+	}
+	while (rpc_d) {
+		ds = rpc_d->next;
+		pkg_free(rpc_d);
+		rpc_d = ds;
+	}
+	return;
+}
+
+
+/**
+ */
+static struct rpc_data_struct *new_data_struct(rpc_ctx_t* ctx)
+{
+	struct rpc_data_struct *ds;
+
+	if (!ctx) return NULL;
+	ds = (struct rpc_data_struct*)pkg_malloc(sizeof(struct rpc_data_struct));
+	if (!ds) {
+		rpc_fault(ctx, 500, "Internal Server Error (oom)");
+		return NULL;
+	}
+	memset(ds, 0, sizeof(struct rpc_data_struct));
+	ds->ctx = ctx;
+
+	return ds;
+}
+
+
+/** Initialize xhttp_rpc reply data structure.
+ *
+ * This function initializes the data structure that contains all data related
+ * to the xhttp_rpc reply being created. The function must be called before any
+ * other function that adds data to the reply.
+ * @param ctx rpc_ctx_t structure to be initialized.
+ * @return 0 on success, a negative number on error.
+ */
+static int init_xhttp_rpc_reply(rpc_ctx_t *ctx)
+{
+	struct xhttp_rpc_reply *reply = &ctx->reply;
+
+	reply->code = 200;
+	reply->reason = XHTTP_RPC_REASON_OK;
+	reply->buf.s = pkg_malloc(buf_size);
+	if (!reply->buf.s) {
+		LM_ERR("oom\n");
+		rpc_fault(ctx, 500, "Internal Server Error (No memory left)");
+		return -1;
+	}
+	reply->buf.len = buf_size;
+	reply->body.s = reply->buf.s;
+	reply->body.len = 0;
+	return 0;
+}
+
+
+/** Implementation of rpc_send function required by the management API.
+ *
+ * This is the function that will be called whenever a management function
+ * asks the management interface to send the reply to the client.
+ * The SIP/HTTP reply sent to
+ * the client will be always 200 OK, if an error ocurred on the server then it
+ * will be indicated in the html document in body.
+ *
+ * @param ctx A pointer to the context structure of the xhttp_rpc request that
+ *            generated the reply.
+ * @return 1 if the reply was already sent, 0 on success, a negative number on
+ *            error
+ */
+static int rpc_send(rpc_ctx_t* ctx)
+{
+	struct xhttp_rpc_reply* reply;
+
+	if (ctx->reply_sent) return 1;
+
+	reply = &ctx->reply;
+
+	if (0!=xhttp_rpc_build_page(ctx)){
+		rpc_fault(ctx, 500, "Internal Server Error");
+	}
+
+	ctx->reply_sent = 1;
+	if (reply->body.len)
+		xhttp_api.reply(ctx->msg, reply->code, &reply->reason,
+			&XHTTP_RPC_CONTENT_TYPE_TEXT_HTML, &reply->body);
+	else
+		xhttp_api.reply(ctx->msg, reply->code, &reply->reason,
+			&XHTTP_RPC_CONTENT_TYPE_TEXT_HTML, &reply->reason);
+
+	if (reply->buf.s) {
+		pkg_free(reply->buf.s);
+		reply->buf.s = NULL;
+		reply->buf.len = 0;
+	}
+	if (ctx->arg.s) {
+		pkg_free(ctx->arg.s);
+		ctx->arg.s = NULL;
+		ctx->arg.len = 0;
+	}
+	if (ctx->data_structs) {
+		free_data_struct(ctx->data_structs);
+		ctx->data_structs = NULL;
+	}
+
+	return 0;
+}
+
+
+/** Converts the variables provided in parameter ap according to formatting
+ * string provided in parameter fmt into HTML format.
+ *
+ * This function takes the parameters provided in ap parameter and creates
+ * HTML formatted parameters that will be put in the html document.
+ * The format of input parameters is described in formatting string
+ * fmt which follows the syntax of the management API. In the case of
+ * an error the function will generate an error reply in err_reply parameter
+ * instead.
+ * @param ctx An error reply document will be generated here if the
+ *                  function encounters a problem while processing input
+ *                  parameters.
+ * @param fmt Formatting string of the management API.
+ * @param ap A pointer to the array of input parameters.
+ *
+ */
+static int print_value(rpc_ctx_t* ctx, char fmt, va_list* ap, str *id)
+
+{
+	str body;
+	str *sp;
+	char buf[PRINT_VALUE_BUF_LEN];
+	time_t dt;
+	struct tm* t;
+
+	switch(fmt) {
+	case 'd':
+		body.s = sint2str(va_arg(*ap, int), &body.len);
+		break;
+	case 'f':
+		body.s = buf;
+		body.len = snprintf(buf, PRINT_VALUE_BUF_LEN,
+				"%f", va_arg(*ap, double));
+		if (body.len < 0) {
+			LM_ERR("Error while converting double\n");
+			return -1;
+		}
+		break;
+	case 'b':
+		body.len = 1;
+		body.s = ((va_arg(*ap, int)==0)?"0":"1");
+		break;
+	case 't':
+		body.s = buf;
+		body.len = sizeof("19980717T14:08:55") - 1;
+		dt = va_arg(*ap, time_t);
+		t = gmtime(&dt);
+		if (strftime(buf, PRINT_VALUE_BUF_LEN,
+				"%Y%m%dT%H:%M:%S", t) == 0) {
+			LM_ERR("Error while converting time\n");
+			return -1;
+		}
+		break;
+	case 's':
+		body.s = va_arg(*ap, char*);
+		body.len = strlen(body.s);
+		break;
+	case 'S':
+		sp = va_arg(*ap, str*);
+		body = *sp;
+		break;
+	default:
+		body.len = 0;
+		body.s = NULL;
+		LM_ERR("Invalid formatting character [%c]\n", fmt);
+		return -1;
+	}
+	if (0!=xhttp_rpc_build_content(ctx, &body, id)) {
+		rpc_fault(ctx, 500, "Internal Server Error");
+		return -1;
+	}
+	return 0;
+}
+
+
+/** Implementation of rpc_add function required by the management API.
+ *
+ * This function will be called when an RPC management function calls
+ * rpc->add to add a parameter to the xhttp_rpc reply being generated.
+ */
+static int rpc_add(rpc_ctx_t* ctx, char* fmt, ...)
+{
+	void **void_ptr;
+	struct rpc_data_struct *ds;
+	va_list ap;
+
+	if (0!=xhttp_rpc_build_content(ctx, NULL, NULL)) {
+		rpc_fault(ctx, 500, "Internal Server Error");
+		return -1;
+	}
+	va_start(ap, fmt);
+	while(*fmt) {
+		if (*fmt == '{') {
+			void_ptr = va_arg(ap, void**);
+			ds = new_data_struct(ctx);
+			if (!ds) goto err;
+			if (ctx->data_structs) free_data_struct(ctx->data_structs);
+			ctx->data_structs = ds;
+			*void_ptr = ds;
+		} else {
+			if (print_value(ctx, *fmt, &ap, NULL) < 0) goto err;
+		}
+		fmt++;
+	}
+	va_end(ap);
+	return 0;
+err:
+	va_end(ap);
+	return -1;
+}
+
+
+/** Implementation of rpc->scan function required by the management API.
+ *
+ * This is the function that will be called whenever a management function
+ * calls rpc->scan to get the value of parameter from the xhttp_rpc
+ * request. This function will extract the current parameter from the xhttp_rpc
+ * URL and attempts to convert it to the type requested by the management
+ * function that called it.
+ */
+static int rpc_scan(rpc_ctx_t* ctx, char* fmt, ...)
+{
+	int *int_ptr;
+	char **char_ptr;
+	double *double_ptr;
+	str *str_ptr;
+
+	str arg;
+
+	int mandatory_param = 1;
+	int modifiers = 0;
+	char* orig_fmt;
+	va_list ap;
+
+	orig_fmt=fmt;
+	va_start(ap, fmt);
+	while(*fmt) {
+		switch(*fmt) {
+		case '*': /* start of optional parameters */
+			mandatory_param = 0;
+			modifiers++;
+			fmt++;
+			continue;
+			break;
+		case '.': /* autoconvert */
+			modifiers++;
+			fmt++;
+			continue;
+			break;
+		case 'b': /* Bool */
+		case 't': /* Date and time */
+		case 'd': /* Integer */
+			xhttp_rpc_get_next_arg(ctx, &arg);
+			if (arg.len==0)
+				goto read_error;
+			int_ptr = va_arg(ap, int*);
+			*int_ptr = strtol(arg.s, 0, 0);
+			break;
+		case 'f': /* double */
+			xhttp_rpc_get_next_arg(ctx, &arg);
+			if (arg.len==0)
+				goto read_error;
+			double_ptr = va_arg(ap, double*);
+			*double_ptr = strtod(arg.s, 0);
+			break;
+		case 's': /* zero terminated string */
+			xhttp_rpc_get_next_arg(ctx, &arg);
+			if (arg.len==0)
+				goto read_error;
+			char_ptr = va_arg(ap, char**);
+			*char_ptr = arg.s;
+			break;
+		case 'S': /* str structure */
+			xhttp_rpc_get_next_arg(ctx, &arg);
+			if (arg.len==0)
+				goto read_error;
+			str_ptr = va_arg(ap, str*);
+			*str_ptr = arg;
+			break;
+		case '{':
+			xhttp_rpc_get_next_arg(ctx, &arg);
+			if (arg.len==0)
+				goto read_error;
+			LM_ERR("Unsupported param type [{]\n");
+			rpc_fault(ctx, 500, "Unsupported param type [{]");
+			goto error;
+			break;
+		default:
+			LM_ERR("Invalid param type in formatting string: [%c]\n", *fmt);
+			rpc_fault(ctx, 500,
+				"Internal Server Error (inval formatting str)");
+			goto error;
+		}
+		fmt++;
+	}
+	va_end(ap);
+	return (int)(fmt-orig_fmt)-modifiers;
+read_error:
+	if (mandatory_param) rpc_fault(ctx, 400, "Invalid parameter value");
+error:
+	va_end(ap);
+	return -((int)(fmt-orig_fmt)-modifiers);
+}
+
+
+/** Implementation of rpc_printf function required by the management API.
+ *
+ * This function will be called whenever an RPC management function calls
+ * rpc-printf to add a parameter to the xhttp_rpc reply being constructed.
+ */
+static int rpc_printf(rpc_ctx_t* ctx, char* fmt, ...)
+{
+	int n, size;
+	char *p;
+	va_list ap;
+
+	if (0!=xhttp_rpc_build_content(ctx, NULL, NULL)) {
+		rpc_fault(ctx, 500, "Internal Server Error");
+		return -1;
+	}
+
+	p = ctx->reply.body.s + ctx->reply.body.len;
+	size = ctx->reply.buf.len - ctx->reply.body.len;
+	va_start(ap, fmt);
+	n = vsnprintf(p, size, fmt, ap);
+	va_end(ap);
+	if (n > -1 && n < size) {
+		ctx->reply.body.len += n;
+		p += n;
+	} else {
+		LM_ERR("oom\n");
+		rpc_fault(ctx, 500, "Internal Server Error (oom)");
+		return -1;
+	}
+	if (0!=xhttp_rpc_insert_break(ctx)) {
+		LM_ERR("oom\n");
+		rpc_fault(ctx, 500, "Internal Server Error (oom)");
+		return -1;
+	}
+
+	return 0;
+}
+
+
+/** Adds a new member to structure.
+ */
+static int rpc_struct_add(struct rpc_data_struct* rpc_s, char* fmt, ...)
+{
+	va_list ap;
+	void **void_ptr;
+	str member_name;
+	rpc_ctx_t *ctx = rpc_s->ctx;
+	struct rpc_data_struct *ds, *s;
+
+	if (!ctx) {
+		LM_ERR("Invalid context\n");
+		return -1;
+	}
+	if (!ctx->data_structs) {
+		LM_ERR("Invalid structs\n");
+		return -1;
+	}
+	s = ds = ctx->data_structs;
+	ctx->struc_depth = 0;
+	while (s) {
+		if (s == rpc_s) {
+			if (s->next) {
+				free_data_struct(s->next);
+				s->next = NULL;
+			}
+			break;
+		}
+		ctx->struc_depth++;
+		ds = s;
+		s = s->next;
+	}
+	if (!s)
+		s = ds;
+	va_start(ap, fmt);
+	while(*fmt) {
+		member_name.s = va_arg(ap, char*);
+		member_name.len = (member_name.s?strlen(member_name.s):0);
+		if (*fmt == '{') {
+			void_ptr = va_arg(ap, void**);
+			ds = new_data_struct(ctx);
+			if (!ds) goto err;
+			s->next = ds;
+			*void_ptr = ds;
+			if (0!=xhttp_rpc_build_content(ctx, NULL, &member_name))
+				goto err;
+		} else {
+			if (print_value(ctx, *fmt, &ap, &member_name) < 0) goto err;
+		}
+		fmt++;
+	}
+	va_end(ap);
+	return 0;
+err:
+	va_end(ap);
+	return -1;
+}
+
+
+static int rpc_struct_scan(void* s, char* fmt, ...)
+{
+	LM_ERR("Not implemented\n");
+	return -1;
+}
+
+
+/** Create a new member from formatting string and add it to a structure.
+ */
+static int rpc_struct_printf(void* s, char* member_name, char* fmt, ...)
+{
+	LM_ERR("Not implemented\n");
+	return -1;
+}
+
+
+/** Returns the RPC capabilities supported by the xmlrpc driver.
+ */
+static rpc_capabilities_t rpc_capabilities(rpc_ctx_t* ctx)
+{
+	/* No support for async commands.
+	 */
+	return 0;
+}
+
+
+/** Returns a new "delayed reply" context.
+ * Creates a new delayed reply context in shm and returns it.
+ * @return 0 - not supported, already replied, or no more memory;
+ *         !=0 pointer to the special delayed ctx.
+ * Note1: one should use the returned ctx reply context to build a reply and
+ *  when finished call rpc_delayed_ctx_close().
+ * Note2: adding pieces to the reply in different processes is not supported.
+ */
+static struct rpc_delayed_ctx* rpc_delayed_ctx_new(rpc_ctx_t* ctx)
+{
+	return NULL;
+}
+
+
+/** Closes a "delayed reply" context and sends the reply.
+ * If no reply has been sent the reply will be built and sent automatically.
+ * See the notes from rpc_new_delayed_ctx()
+ */
+static void rpc_delayed_ctx_close(struct rpc_delayed_ctx* dctx)
+{
+	return;
+}
+
+
+static int mod_init(void)
+{
+	int i;
+
+	/* bind the XHTTP API */
+	if (xhttp_load_api(&xhttp_api) < 0) {
+		LM_ERR("cannot bind to XHTTP API\n");
+		return -1;
+	}
+
+	/* Check xhttp_rpc_buf_size param */
+	if (buf_size == 0)
+		buf_size = pkg_mem_size/3;
+
+	/* Check xhttp_rpc_root param */
+	xhttp_rpc_root.len = strlen(xhttp_rpc_root.s);
+	for(i=0;i<xhttp_rpc_root.len;i++){
+		if ( !isalnum(xhttp_rpc_root.s[i]) && xhttp_rpc_root.s[i]!='_') {
+			LM_ERR("bad xhttp_rpc_root param [%.*s], char [%c] "
+				"- use only alphanumerical chars\n",
+				xhttp_rpc_root.len, xhttp_rpc_root.s,
+				xhttp_rpc_root.s[i]);
+			return -1;
+		}
+	}
+
+	func_param.send = (rpc_send_f)rpc_send;
+	func_param.fault = (rpc_fault_f)rpc_fault;
+	func_param.add = (rpc_add_f)rpc_add;
+	func_param.scan = (rpc_scan_f)rpc_scan;
+	func_param.printf = (rpc_printf_f)rpc_printf;
+	func_param.struct_add = (rpc_struct_add_f)rpc_struct_add;
+	func_param.struct_scan = (rpc_struct_scan_f)rpc_struct_scan;
+	func_param.struct_printf = (rpc_struct_printf_f)rpc_struct_printf;
+	func_param.capabilities = (rpc_capabilities_f)rpc_capabilities;
+	func_param.delayed_ctx_new = (rpc_delayed_ctx_new_f)rpc_delayed_ctx_new;
+	func_param.delayed_ctx_close =
+		(rpc_delayed_ctx_close_f)rpc_delayed_ctx_close;
+
+	return 0;
+}
+
+static int child_init(int rank)
+{
+	int i, j;
+	int len;
+	xhttp_rpc_mod_cmds_t *cmds;
+	/* rpc_export_t *rpc_e; */
+
+	if(rank==PROC_MAIN || rank==PROC_TCP_MAIN)
+		return 0; /* do nothing for the main process */
+
+	if (rank==PROC_INIT)
+	{
+		/* building a cache of rpc module commands */
+		xhttp_rpc_mod_cmds =
+			(xhttp_rpc_mod_cmds_t*)pkg_malloc(sizeof(xhttp_rpc_mod_cmds_t));
+		if (xhttp_rpc_mod_cmds==NULL){
+			LM_ERR("oom\n");
+			return -1;
+		}
+		xhttp_rpc_mod_cmds->rpc_e_index = 0;
+		xhttp_rpc_mod_cmds->mod.s = NULL;
+		xhttp_rpc_mod_cmds->mod.len = 0;
+		xhttp_rpc_mod_cmds->size = 0;
+		xhttp_rpc_mod_cmds_size = 1;
+		cmds = xhttp_rpc_mod_cmds;
+		for(i=0; i<rpc_sarray_crt_size; i++){
+			len = strlen(rpc_sarray[i]->name);
+			j = 0;
+			while (j<len && rpc_sarray[i]->name[j]!='.')
+				j++;
+			if (j==len) {
+				LM_DBG("dropping invalid command format [%.*s]\n",
+						len, rpc_sarray[i]->name);
+			} else {
+				if (cmds->mod.len==0) {
+					/* this is the first module */
+					cmds->rpc_e_index = i;
+					cmds->mod.s = (char*)&rpc_sarray[i]->name[0];
+					cmds->mod.len = j;
+					cmds->size++;
+				} else if (cmds->mod.len==j &&
+					strncmp(cmds->mod.s,
+						(char*)&rpc_sarray[i]->name[0],
+						j)==0){
+					cmds->size++;
+				} else {
+					cmds = (xhttp_rpc_mod_cmds_t*)
+						pkg_realloc(xhttp_rpc_mod_cmds,
+							(xhttp_rpc_mod_cmds_size+1)*
+							sizeof(xhttp_rpc_mod_cmds_t));
+					if (cmds==NULL){
+						LM_ERR("oom\n");
+						return -1;
+					}
+					xhttp_rpc_mod_cmds = cmds;
+					cmds = &xhttp_rpc_mod_cmds[xhttp_rpc_mod_cmds_size];
+					cmds->rpc_e_index = i;
+					cmds->mod.s = (char*)&rpc_sarray[i]->name[0];
+					cmds->mod.len = j;
+					xhttp_rpc_mod_cmds_size++;
+					cmds->size = 1;
+				}
+			}
+		}
+		/*
+		for(i=0; i<xhttp_rpc_mod_cmds_size; i++){
+			for (j=0; j<xhttp_rpc_mod_cmds[i].size; j++){
+				rpc_e = rpc_sarray[xhttp_rpc_mod_cmds[i].rpc_e_index+j];
+				LM_DBG("[%p] => [%p]->[%.*s] [%p]->[%s]\n",
+					rpc_e,
+					xhttp_rpc_mod_cmds[i].mod.s,
+					xhttp_rpc_mod_cmds[i].mod.len,
+					xhttp_rpc_mod_cmds[i].mod.s,
+					rpc_e->name,
+					rpc_e->name);
+			}
+		}
+		*/
+	}
+
+	full_version_len = strlen(full_version);
+	ver_name_len = strlen(ver_name);
+	return 0;
+}
+
+
+static int xhttp_rpc_dispatch(sip_msg_t* msg, char* s1, char* s2)
+{
+	rpc_export_t* rpc_e;
+	str arg = {NULL, 0};
+	int ret = 0;
+	int i;
+
+	if(!IS_HTTP(msg)) {
+		LM_DBG("Got non HTTP msg\n");
+		return NONSIP_MSG_PASS;
+	}
+
+	/* Init xhttp_rpc context */
+	if (ctx.reply.buf.s) LM_ERR("Unexpected buf value [%p][%d]\n",
+				ctx.reply.buf.s, ctx.reply.buf.len);
+	memset(&ctx, 0, sizeof(rpc_ctx_t));
+	ctx.msg = msg;
+	ctx.mod = ctx.cmd = -1;
+	if (init_xhttp_rpc_reply(&ctx) < 0) goto send_reply;
+
+	/* Extract arguments from url */
+	if (0!=xhttp_rpc_parse_url(&msg->first_line.u.request.uri,
+				&ctx.mod, &ctx.cmd, &arg)){
+		rpc_fault(&ctx, 500, "Bad URL");
+		goto send_reply;
+	}
+
+	if (arg.s) {
+		if (arg.len) {
+			/* Unescape args */
+			ctx.arg.s = pkg_malloc((arg.len+1)*sizeof(char));
+			if (ctx.arg.s==NULL){
+				LM_ERR("oom\n");
+				rpc_fault(&ctx, 500, "Internal Server Error (oom)");
+				goto send_reply;
+			}
+			for(i=0;i<arg.len;i++) if (arg.s[i]=='+') arg.s[i]=' ';
+			if (0>un_escape(&arg, &ctx.arg)) {
+				LM_ERR("unable to escape [%.*s]\n", arg.len, arg.s);
+				rpc_fault(&ctx, 500, "Bad arg in URL");
+				goto send_reply;
+			}
+			ctx.arg.s[ctx.arg.len] = '\0';
+			ctx.arg.len++;
+			ctx.arg2scan = ctx.arg;
+		}
+		ctx.arg_received = 1;
+	} else {
+		goto send_reply;
+	}
+	
+	/*
+	rpc_e=find_rpc_export((char*)rpc_sarray[xhttp_rpc_mod_cmds[ctx.mod].rpc_e_index+ctx.cmd]->name, 0);
+	if ((rpc_e==NULL) || (rpc_e->function==NULL)){
+		LM_ERR("Unable to find rpc command [%s]\n",
+		rpc_sarray[xhttp_rpc_mod_cmds[ctx.mod].rpc_e_index+ctx.cmd]->name);
+		rpc_fault(&ctx, 500, "Method not found");
+		goto send_reply;
+	}
+	*/
+	rpc_e=rpc_sarray[xhttp_rpc_mod_cmds[ctx.mod].rpc_e_index+ctx.cmd];
+	rpc_e->function(&func_param, &ctx);
+
+send_reply:
+	if (!ctx.reply_sent) {
+		ret = rpc_send(&ctx);
+	}
+	if (ret < 0) return -1;
+	return 0;
+}
+

+ 94 - 0
modules/xhttp_rpc/xhttp_rpc.h

@@ -0,0 +1,94 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2011 VoIP Embedded, Inc.
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * 2011-11-11  initial version (osas)
+ */
+
+
+#ifndef _XHTTP_RPC_H
+#define _XHTTP_RPC_H
+
+#include "../../str.h"
+#include "../../rpc_lookup.h"
+#include "../../parser/msg_parser.h"
+
+
+#define ERROR_REASON_BUF_LEN 1024
+#define PRINT_VALUE_BUF_LEN 256
+
+
+
+struct rpc_data_struct {
+	struct rpc_ctx* ctx;
+	struct rpc_data_struct* next;
+};
+
+
+/** Representation of the xhttp_rpc reply being constructed.
+ *
+ * This data structure describes the xhttp_rpc reply that is being constructed
+ * and will be sent to the client.
+ */
+struct xhttp_rpc_reply {
+	int code;	/**< Reply code which indicates the type of the reply */
+	str reason;	/**< Reason phrase text which provides human-readable
+			 * description that augments the reply code */
+	str body;	/**< The xhttp_rpc http body built so far */
+	str buf;	/**< The memory buffer allocated for the reply, this is
+			 * where the body attribute of the structure points to */
+};
+
+
+/** The context of the xhttp_rpc request being processed.
+ *
+ * This is the data structure that contains all data related to the xhttp_rpc
+ * request being processed, such as the reply code and reason, data to be sent
+ * to the client in the reply, and so on.
+ *
+ * There is always one context per xhttp_rpc request.
+ */
+typedef struct rpc_ctx {
+	sip_msg_t* msg;			/**< The SIP/HTTP received message. */
+	struct xhttp_rpc_reply reply;	/**< xhttp_rpc reply to be sent to the client */
+	int reply_sent;
+	int mod;			/**< Module being processed */
+	int cmd;			/**< RPC command being processed */
+	int arg_received;		/**< RPC argument flag */
+	str arg;			/**< RPC command argument */
+	str arg2scan;			/**< RPC command args to be parsed */
+	struct rpc_struct *structs;
+	struct rpc_data_struct *data_structs;
+	unsigned int struc_depth;
+} rpc_ctx_t;
+
+
+/* An RPC module representation.
+ *
+ * The module is the first substring of the RPC commands (delimited by '.'.
+ */
+typedef struct xhttp_rpc_mod_cmds_ {
+	int rpc_e_index;	/**< Index to the first module RPC rec in rpc_sarray */
+	str mod;		/**< Module name */
+	int size;		/**< Number of commands provided by the above module */
+} xhttp_rpc_mod_cmds_t;
+
+#endif
+

+ 621 - 0
modules/xhttp_rpc/xhttp_rpc_fnc.c

@@ -0,0 +1,621 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2011 VoIP Embedded, Inc.
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * 2011-11-11  initial version (osas)
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#include "../../trim.h"
+#include "../../ver.h"
+#include "../../rpc_lookup.h"
+#include "xhttp_rpc.h"
+
+extern str xhttp_rpc_root;
+extern int xhttp_rpc_mod_cmds_size;
+extern xhttp_rpc_mod_cmds_t *xhttp_rpc_mod_cmds;
+
+extern int ver_name_len;
+extern int full_version_len;
+
+#define XHTTP_RPC_COPY(p,str)	\
+do{	\
+	if ((int)((p)-buf)+(str).len>max_page_len) {	\
+		goto error;	\
+	}	\
+	memcpy((p), (str).s, (str).len); (p) += (str).len;	\
+}while(0)
+
+#define XHTTP_RPC_COPY_2(p,str1,str2)	\
+do{	\
+	if ((int)((p)-buf)+(str1).len+(str2).len>max_page_len) {	\
+		goto error;	\
+	}	\
+	memcpy((p), (str1).s, (str1).len); (p) += (str1).len;	\
+	memcpy((p), (str2).s, (str2).len); (p) += (str2).len;	\
+}while(0)
+
+#define XHTTP_RPC_COPY_3(p,str1,str2,str3)	\
+do{	\
+	if ((int)((p)-buf)+(str1).len+(str2).len+(str3).len>max_page_len) {	\
+		goto error;	\
+	}	\
+	memcpy((p), (str1).s, (str1).len); (p) += (str1).len;	\
+	memcpy((p), (str2).s, (str2).len); (p) += (str2).len;	\
+	memcpy((p), (str3).s, (str3).len); (p) += (str3).len;	\
+}while(0)
+
+#define XHTTP_RPC_COPY_4(p,str1,str2,str3,str4)	\
+do{	\
+	if ((int)((p)-buf)+(str1).len+(str2).len+(str3).len+(str4).len>max_page_len) {	\
+		goto error;	\
+	}	\
+	memcpy((p), (str1).s, (str1).len); (p) += (str1).len;	\
+	memcpy((p), (str2).s, (str2).len); (p) += (str2).len;	\
+	memcpy((p), (str3).s, (str3).len); (p) += (str3).len;	\
+	memcpy((p), (str4).s, (str4).len); (p) += (str4).len;	\
+}while(0)
+
+#define XHTTP_RPC_COPY_5(p,s1,s2,s3,s4,s5)	\
+do{	\
+	if ((int)((p)-buf)+(s1).len+(s2).len+(s3).len+(s4).len+(s5).len>max_page_len) {	\
+		goto error;	\
+	}	\
+	memcpy((p), (s1).s, (s1).len); (p) += (s1).len;	\
+	memcpy((p), (s2).s, (s2).len); (p) += (s2).len;	\
+	memcpy((p), (s3).s, (s3).len); (p) += (s3).len;	\
+	memcpy((p), (s4).s, (s4).len); (p) += (s4).len;	\
+	memcpy((p), (s5).s, (s5).len); (p) += (s5).len;	\
+}while(0)
+
+#define XHTTP_RPC_COPY_6(p,s1,s2,s3,s4,s5,s6)	\
+do{	\
+	if ((int)((p)-buf)+(s1).len+(s2).len+(s3).len+(s4).len+(s5).len+(s6).len>max_page_len) {	\
+		goto error;	\
+	}	\
+	memcpy((p), (s1).s, (s1).len); (p) += (s1).len;	\
+	memcpy((p), (s2).s, (s2).len); (p) += (s2).len;	\
+	memcpy((p), (s3).s, (s3).len); (p) += (s3).len;	\
+	memcpy((p), (s4).s, (s4).len); (p) += (s4).len;	\
+	memcpy((p), (s5).s, (s5).len); (p) += (s5).len;	\
+	memcpy((p), (s6).s, (s6).len); (p) += (s6).len;	\
+}while(0)
+
+#define XHTTP_RPC_COPY_10(p,s1,s2,s3,s4,s5,s6,s7,s8,s9,s10)	\
+do{	\
+	if ((int)((p)-buf)+(s1).len+(s2).len+(s3).len+(s4).len+(s5).len+(s6).len+(s7).len+(s8).len+(s9).len+(s10).len>max_page_len) {	\
+		goto error;	\
+	}	\
+	memcpy((p), (s1).s, (s1).len); (p) += (s1).len;	\
+	memcpy((p), (s2).s, (s2).len); (p) += (s2).len;	\
+	memcpy((p), (s3).s, (s3).len); (p) += (s3).len;	\
+	memcpy((p), (s4).s, (s4).len); (p) += (s4).len;	\
+	memcpy((p), (s5).s, (s5).len); (p) += (s5).len;	\
+	memcpy((p), (s6).s, (s6).len); (p) += (s6).len;	\
+	memcpy((p), (s7).s, (s7).len); (p) += (s7).len;	\
+	memcpy((p), (s8).s, (s8).len); (p) += (s8).len;	\
+	memcpy((p), (s9).s, (s9).len); (p) += (s9).len;	\
+	memcpy((p), (s10).s, (s10).len); (p) += (s10).len;	\
+}while(0)
+
+
+static const str XHTTP_RPC_Response_Head_1 = str_init("<html><head><title>");
+static const str XHTTP_RPC_Response_Head_2 = str_init(" RPC Management Interface</title>"\
+        "<style type=\"text/css\">"\
+                "body{margin:0;}body,p,div,td,th,tr,form,ol,ul,li,input,textarea,select,"\
+                "a{font-family:\"lucida grande\",verdana,geneva,arial,helvetica,sans-serif;font-size:14px;}"\
+                "a:hover{text-decoration:none;}a{text-decoration:underline;}"\
+                ".foot{padding-top:40px;font-size:10px;color:#333333;}"\
+                ".foot a{font-size:10px;color:#000000;}"
+                "table.center{margin-left:auto;margin-right:auto;}\n"\
+        "</style>"\
+        "<meta http-equiv=\"Expires\" content=\"0\">"\
+        "<meta http-equiv=\"Pragma\" content=\"no-cache\">");
+
+static const str XHTTP_RPC_Response_Head_3 = str_init(\
+"</head>"\
+"<body alink=\"#000000\" bgcolor=\"#ffffff\" link=\"#000000\" text=\"#000000\" vlink=\"#000000\">");
+
+static const str XHTTP_RPC_Response_Title_Table_1 = str_init(\
+"<table cellspacing=\"0\" cellpadding=\"5\" width=\"100%%\" border=\"0\">"\
+        "<tr bgcolor=\"#BBDDFF\">"\
+        "<td colspan=2 valign=\"top\" align=\"left\" bgcolor=\"#EFF7FF\" width=\"100%%\">"\
+        "<br/><h2 align=\"center\">");
+static const str XHTTP_RPC_Response_Title_Table_2 = str_init(" RPC Interface</h2>"\
+        "<p align=\"center\">");
+static const str XHTTP_RPC_Response_Title_Table_4 = str_init("</p><br/></td></tr></table>\n<center>\n");
+
+static const str XHTTP_RPC_Response_Menu_Table_1 = str_init("<table border=\"0\" cellpadding=\"3\" cellspacing=\"0\"><tbody><tr>\n");
+static const str XHTTP_RPC_Response_Menu_Table_2 = str_init("<td><a href='");
+static const str XHTTP_RPC_Response_Menu_Table_2b = str_init("<td><b><a href='");
+static const str XHTTP_RPC_Response_Menu_Table_3 = str_init("'>");
+static const str XHTTP_RPC_Response_Menu_Table_4 = str_init("</a><td>\n");
+static const str XHTTP_RPC_Response_Menu_Table_4b = str_init("</a></b><td>\n");
+static const str XHTTP_RPC_Response_Menu_Table_5 = str_init("</tr></tbody></table>\n");
+
+static const str XHTTP_RPC_Response_Menu_Cmd_Table_1 = str_init("<table border=\"0\" cellpadding=\"3\" cellspacing=\"0\" width=\"90%\"><tbody>\n");
+static const str XHTTP_RPC_Response_Menu_Cmd_tr_1 = str_init("<tr>\n");
+static const str XHTTP_RPC_Response_Menu_Cmd_td_1a = str_init("   <td width=\"10%\"><a href='");
+static const str XHTTP_RPC_Response_Menu_Cmd_td_3a = str_init("'>");
+static const str XHTTP_RPC_Response_Menu_Cmd_td_4a = str_init("</a></td>\n");
+static const str XHTTP_RPC_Response_Menu_Cmd_td_1b = str_init("   <td align=\"left\"><b>");
+static const str XHTTP_RPC_Response_Menu_Cmd_td_1c = str_init("   <td valign=\"top\" align=\"left\" rowspan=\"");
+static const str XHTTP_RPC_Response_Menu_Cmd_td_1d = str_init("   <td>");
+static const str XHTTP_RPC_Response_Menu_Cmd_td_3c = str_init("\">");
+static const str XHTTP_RPC_Response_Menu_Cmd_td_4b = str_init("</b></td>\n");
+static const str XHTTP_RPC_Response_Menu_Cmd_td_4c = str_init("   </td>\n");
+static const str XHTTP_RPC_Response_Menu_Cmd_td_4d = str_init("</td>\n");
+static const str XHTTP_RPC_Response_Menu_Cmd_tr_2 = str_init("</tr>\n");
+static const str XHTTP_RPC_Response_Menu_Cmd_Table_2 = str_init("</tbody></table>\n");
+
+static const str XHTTP_RPC_NBSP = str_init("&nbsp;");
+static const str XHTTP_RPC_SLASH = str_init("/");
+static const str XHTTP_RPC_SEMICOLON = str_init(": ");
+
+static const str XHTTP_RPC_NODE_INDENT = str_init("\t");
+static const str XHTTP_RPC_NODE_SEPARATOR = str_init(":: ");
+static const str XHTTP_RPC_ATTR_SEPARATOR = str_init(" ");
+static const str XHTTP_RPC_ATTR_VAL_SEPARATOR = str_init("=");
+
+const str XHTTP_RPC_BREAK = str_init("<br/>");
+static const str XHTTP_RPC_CODE_1 = str_init("<pre>");
+static const str XHTTP_RPC_CODE_2 = str_init("</pre>");
+
+static const str XHTTP_RPC_Post_1 = str_init("\n"\
+"               <form name=\"input\" method=\"get\">\n"\
+"                       <input type=\"text\" name=\"arg\"/>\n"\
+"                       <input type=\"submit\" value=\"Submit\"/>\n"\
+"               </form>\n");
+
+static const str XHTTP_RPC_Post_1a = str_init("\n"\
+"               <form name=\"input\" method=\"get\">\n"\
+"                       <textarea name=\"arg\" rows=\"2\" cols=\"60\"></textarea>\n"\
+"                       <input type=\"submit\" value=\"Submit\"/>\n"\
+"               </form>\n");
+
+static const str XHTTP_RPC_Response_Foot = str_init(\
+"\n</center>\n<div align=\"center\" class=\"foot\" style=\"margin:20px auto\">"\
+        "<span style='margin-left:5px;'></span>"\
+        "<a href=\"http://sip-router.org\">SIP Router web site</a> .:. "\
+        "<a href=\"http://www.kamailio.org\">Kamailio web site</a><br/>"\
+        "Copyright &copy; 2011 <a href=\"http://www.voipembedded.com/\">VoIP Embedded</a>"\
+                                                                ". All rights reserved."\
+"</div></body></html>");
+
+#define XHTTP_RPC_ROWSPAN 5
+static const str XHTTP_RPC_CMD_ROWSPAN = str_init("5");
+
+
+
+static const str XHTTP_RPC_ARG = str_init("?arg=");
+str XHTTP_RPC_NULL_ARG = str_init("");
+
+int xhttp_rpc_parse_url(str *http_url, int* mod, int* cmd, str *arg)
+{
+	int index = 0;
+	int i;
+	int mod_len, cmd_len;
+	int url_len = http_url->len;
+	char *url = http_url->s;
+
+	if (url_len==0) {
+		LM_ERR("No URL\n");
+		return -1;
+	}
+	if (url[0] != '/') {
+		LM_ERR("URL starting with [%c] instead of'/'\n", url[0]);
+		return -1;
+	}
+	index++;
+	if (url_len - index < xhttp_rpc_root.len) {
+		LM_ERR("root path 2 short [%.*s]\n", url_len, url);
+		return -1;
+	}
+	if (strncmp(xhttp_rpc_root.s, &url[index], xhttp_rpc_root.len) != 0) {
+		LM_ERR("wrong root path [%.*s]\n", url_len, url);
+		return -1;
+	}
+	if (xhttp_rpc_root.len) {
+		index += xhttp_rpc_root.len;
+		if (url_len - index <= 0)
+			return 0;
+		if (url[index] != '/') {
+			LM_ERR("invalid root path [%s]\n", url);
+			return -1;
+		}
+		index++;
+	}
+	if (index>=url_len)
+		return 0;
+
+	for(i=index;i<url_len && url[i]!='/';i++);
+	mod_len = i - index;
+	for(i=0; i<xhttp_rpc_mod_cmds_size &&
+			!(xhttp_rpc_mod_cmds[i].mod.s[mod_len]=='.' &&
+			strncmp(&url[index],
+				xhttp_rpc_mod_cmds[i].mod.s,
+				mod_len)==0);
+			i++);
+	if (i==xhttp_rpc_mod_cmds_size) {
+		LM_ERR("Invalid mod [%.*s] in url [%s]\n",
+			mod_len, &url[index], url);
+			return -1;
+	}
+	*mod = i;
+
+	index += mod_len;
+	if (index>=url_len)
+		return 0;
+
+	/* skip over '/' */
+	index++;
+
+	/* Looking for "cmd" */
+	if (index>=url_len)
+		return 0;
+	for(i=index;i<url_len && url[i]!='?';i++);
+	cmd_len = i - index;
+	for(i=0;i<xhttp_rpc_mod_cmds[*mod].size &&
+		!(strncmp(&url[index],
+			rpc_sarray[xhttp_rpc_mod_cmds[*mod].rpc_e_index+i]->name,
+			cmd_len) == 0 &&
+		cmd_len==
+		strlen(rpc_sarray[xhttp_rpc_mod_cmds[*mod].rpc_e_index+i]->name));
+		i++);
+	if (i==xhttp_rpc_mod_cmds[*mod].size) {
+		LM_ERR("Invalid cmd [%.*s] in url [%.*s]\n",
+			cmd_len, &url[index], url_len, url);
+		return -1;
+	}
+	*cmd = i;
+	index += cmd_len;
+	if (index>=url_len) return 0;
+	i = url_len - index;
+	if (i<XHTTP_RPC_ARG.len &&
+		(0!=strncmp(&url[index], XHTTP_RPC_ARG.s, XHTTP_RPC_ARG.len))){
+		LM_ERR("Invalid arg string [%.*s]\n", i, &url[index]);
+		return -1;
+	}
+	index += XHTTP_RPC_ARG.len;
+	arg->s = &url[index];
+	arg->len = url_len - index;
+
+	return 0;
+}
+
+
+void xhttp_rpc_get_next_arg(rpc_ctx_t* ctx, str *arg)
+{
+	int i;
+
+	trim_leading(&ctx->arg2scan);
+	if (ctx->arg2scan.len) {
+		*arg = ctx->arg2scan;
+		for(i=1; i<arg->len-1; i++) {
+			if(arg->s[i]==' '||arg->s[i]=='\t'||
+				arg->s[i]=='\r'||arg->s[i]=='\n')
+				break;
+		}
+		arg->len = i;
+		arg->s[i] = '\0';
+		i++;
+		ctx->arg2scan.s += i;
+		ctx->arg2scan.len -= i;
+	} else {
+		*arg = XHTTP_RPC_NULL_ARG;
+	}
+	return;
+}
+
+int xhttp_rpc_build_header(rpc_ctx_t *ctx)
+{
+	int i, j;
+	char *p = ctx->reply.body.s;
+	char *buf = ctx->reply.buf.s;
+	str code;
+	int max_page_len = ctx->reply.buf.len;
+	int mod = ctx->mod;
+	int cmd = ctx->cmd;
+
+	str name;
+
+	str exec_name = {(char*)ver_name, ver_name_len};
+	str server_hdr = {(char*)full_version, full_version_len};
+
+
+	XHTTP_RPC_COPY_10(p,XHTTP_RPC_Response_Head_1,
+			exec_name,
+			XHTTP_RPC_Response_Head_2,
+			XHTTP_RPC_Response_Head_3,
+			XHTTP_RPC_Response_Title_Table_1,
+			exec_name,
+			XHTTP_RPC_Response_Title_Table_2,
+			server_hdr,
+			XHTTP_RPC_Response_Title_Table_4,
+			/* Building module menu */
+			XHTTP_RPC_Response_Menu_Table_1);
+	for(i=0;i<xhttp_rpc_mod_cmds_size;i++) {
+		if(i!=mod) {
+			XHTTP_RPC_COPY(p,XHTTP_RPC_Response_Menu_Table_2);
+		} else {
+			XHTTP_RPC_COPY(p,XHTTP_RPC_Response_Menu_Table_2b);
+		}
+		XHTTP_RPC_COPY(p,XHTTP_RPC_SLASH);
+		if (xhttp_rpc_root.len) {
+			XHTTP_RPC_COPY_2(p,xhttp_rpc_root,XHTTP_RPC_SLASH);
+		}
+		XHTTP_RPC_COPY_3(p,xhttp_rpc_mod_cmds[i].mod,
+				XHTTP_RPC_Response_Menu_Table_3,
+				xhttp_rpc_mod_cmds[i].mod);
+		if(i!=mod) {
+			XHTTP_RPC_COPY(p,XHTTP_RPC_Response_Menu_Table_4);
+		} else {
+			XHTTP_RPC_COPY(p,XHTTP_RPC_Response_Menu_Table_4b);
+		}
+	}
+	XHTTP_RPC_COPY(p,XHTTP_RPC_Response_Menu_Table_5);
+
+	if (ctx->arg_received) { /* Build an rpc reply */
+		name.s =
+		(char*)rpc_sarray[xhttp_rpc_mod_cmds[mod].rpc_e_index+cmd]->name;
+		name.len = strlen(name.s);
+		/* Print comand name */
+		XHTTP_RPC_COPY_4(p,XHTTP_RPC_Response_Menu_Cmd_Table_1,
+				XHTTP_RPC_Response_Menu_Cmd_tr_1,
+				XHTTP_RPC_Response_Menu_Cmd_td_1a,
+				XHTTP_RPC_SLASH);
+		if (xhttp_rpc_root.len) {
+			XHTTP_RPC_COPY_2(p,xhttp_rpc_root, XHTTP_RPC_SLASH);
+		}
+		XHTTP_RPC_COPY_6(p,xhttp_rpc_mod_cmds[mod].mod,
+				XHTTP_RPC_SLASH,
+				name,
+				XHTTP_RPC_Response_Menu_Cmd_td_3a,
+				name,
+				XHTTP_RPC_Response_Menu_Cmd_td_4a);
+		/* Print response code */
+		XHTTP_RPC_COPY(p,XHTTP_RPC_Response_Menu_Cmd_td_1d);
+		code.s = int2str((unsigned long)ctx->reply.code, &code.len);
+		XHTTP_RPC_COPY_10(p,code,
+				XHTTP_RPC_SEMICOLON,
+				ctx->reply.reason,
+				XHTTP_RPC_Response_Menu_Cmd_td_4d,
+				XHTTP_RPC_Response_Menu_Cmd_tr_2,
+				XHTTP_RPC_Response_Menu_Cmd_tr_1,
+				XHTTP_RPC_Response_Menu_Cmd_td_1d,
+				XHTTP_RPC_Response_Menu_Cmd_td_4d,
+				XHTTP_RPC_Response_Menu_Cmd_td_1d,
+				XHTTP_RPC_CODE_1);
+	} else if (mod>=0) { /* Building command menu */
+		if (ctx->reply.body.len==0 && ctx->reply.code!=200) {
+			code.s = int2str((unsigned long)ctx->reply.code, &code.len);
+			XHTTP_RPC_COPY_5(p,XHTTP_RPC_CODE_1,
+					code,
+					XHTTP_RPC_SEMICOLON,
+					ctx->reply.reason,
+					XHTTP_RPC_CODE_2);
+		} else {
+			name.s =
+			(char*)rpc_sarray[xhttp_rpc_mod_cmds[mod].rpc_e_index]->name;
+			name.len = strlen(name.s);
+			/* Build the list of comands for the selected module */
+			XHTTP_RPC_COPY_4(p,XHTTP_RPC_Response_Menu_Cmd_Table_1,
+					XHTTP_RPC_Response_Menu_Cmd_tr_1,
+					XHTTP_RPC_Response_Menu_Cmd_td_1a,
+					XHTTP_RPC_SLASH);
+			if (xhttp_rpc_root.len) {
+				XHTTP_RPC_COPY_2(p,xhttp_rpc_root,XHTTP_RPC_SLASH);
+			}
+			XHTTP_RPC_COPY_6(p,xhttp_rpc_mod_cmds[mod].mod,
+					XHTTP_RPC_SLASH,
+					name,
+					XHTTP_RPC_Response_Menu_Cmd_td_3a,
+					name,
+					XHTTP_RPC_Response_Menu_Cmd_td_4a);
+			if (cmd>=0) {
+				name.s =
+			(char*)rpc_sarray[xhttp_rpc_mod_cmds[mod].rpc_e_index+cmd]->name;
+				name.len = strlen(name.s);
+				XHTTP_RPC_COPY_3(p,XHTTP_RPC_Response_Menu_Cmd_td_1b,
+						name,
+						XHTTP_RPC_Response_Menu_Cmd_td_4b);
+			}
+			XHTTP_RPC_COPY(p,XHTTP_RPC_Response_Menu_Cmd_tr_2);
+			for(j=1;j<xhttp_rpc_mod_cmds[mod].size;j++) {
+				name.s =
+			(char*)rpc_sarray[xhttp_rpc_mod_cmds[mod].rpc_e_index+j]->name;
+				name.len = strlen(name.s);
+				XHTTP_RPC_COPY_3(p,XHTTP_RPC_Response_Menu_Cmd_tr_1,
+						XHTTP_RPC_Response_Menu_Cmd_td_1a,
+						XHTTP_RPC_SLASH);
+				if (xhttp_rpc_root.len) {
+					XHTTP_RPC_COPY_2(p,xhttp_rpc_root, XHTTP_RPC_SLASH);
+				}
+				XHTTP_RPC_COPY_6(p,xhttp_rpc_mod_cmds[mod].mod,
+						XHTTP_RPC_SLASH,
+						name,
+						XHTTP_RPC_Response_Menu_Cmd_td_3a,
+						name,
+						XHTTP_RPC_Response_Menu_Cmd_td_4a);
+				if (cmd>=0){
+					if (j==1) {
+						XHTTP_RPC_COPY_5(p,
+							XHTTP_RPC_Response_Menu_Cmd_td_1c,
+							XHTTP_RPC_CMD_ROWSPAN,
+							XHTTP_RPC_Response_Menu_Cmd_td_3c,
+							XHTTP_RPC_Post_1,
+							XHTTP_RPC_Response_Menu_Cmd_td_4c);
+					} else if (j>XHTTP_RPC_ROWSPAN) {
+						XHTTP_RPC_COPY_3(p,
+							XHTTP_RPC_Response_Menu_Cmd_td_1d,
+							XHTTP_RPC_NBSP,
+							XHTTP_RPC_Response_Menu_Cmd_td_4d);
+					}
+				}
+				XHTTP_RPC_COPY(p,XHTTP_RPC_Response_Menu_Cmd_tr_2);
+			}
+			if (cmd>=0){
+				if (j==1) {
+					XHTTP_RPC_COPY_10(p,XHTTP_RPC_Response_Menu_Cmd_tr_1,
+							XHTTP_RPC_Response_Menu_Cmd_td_1d,
+							XHTTP_RPC_NBSP,
+							XHTTP_RPC_Response_Menu_Cmd_td_4d,
+							XHTTP_RPC_Response_Menu_Cmd_td_1c,
+							XHTTP_RPC_CMD_ROWSPAN,
+							XHTTP_RPC_Response_Menu_Cmd_td_3c,
+							XHTTP_RPC_Post_1,
+							XHTTP_RPC_Response_Menu_Cmd_td_4c,
+							XHTTP_RPC_Response_Menu_Cmd_tr_2);
+					j++;
+				}
+				for(;j<=XHTTP_RPC_ROWSPAN;j++) {
+					XHTTP_RPC_COPY_5(p,XHTTP_RPC_Response_Menu_Cmd_tr_1,
+							XHTTP_RPC_Response_Menu_Cmd_td_1d,
+							XHTTP_RPC_NBSP,
+							XHTTP_RPC_Response_Menu_Cmd_td_4d,
+							XHTTP_RPC_Response_Menu_Cmd_tr_2);
+				}
+			}
+		}
+		XHTTP_RPC_COPY_2(p,XHTTP_RPC_Response_Menu_Cmd_Table_2,
+				XHTTP_RPC_Response_Foot);
+	} else {
+		if (ctx->reply.body.len==0 && ctx->reply.code!=200) {
+			code.s = int2str((unsigned long)ctx->reply.code, &code.len);
+			XHTTP_RPC_COPY_5(p,XHTTP_RPC_CODE_1,
+					code,
+					XHTTP_RPC_SEMICOLON,
+					ctx->reply.reason,
+					XHTTP_RPC_CODE_2);
+		}
+		XHTTP_RPC_COPY(p,XHTTP_RPC_Response_Foot);
+	}
+
+	ctx->reply.body.len = p - ctx->reply.body.s;
+	return 0;
+
+
+error:
+	LM_ERR("buffer 2 small\n");
+	ctx->reply.body.len = p - ctx->reply.body.s;
+	return -1;
+}
+
+
+int xhttp_rpc_build_content(rpc_ctx_t *ctx, str *val, str *id)
+{
+	char *p;
+	char *buf = ctx->reply.buf.s;
+	int max_page_len = ctx->reply.buf.len;
+	int i;
+
+	if (ctx->reply.body.len==0)
+		if (0!=xhttp_rpc_build_header(ctx))
+			return -1;
+
+	p = ctx->reply.body.s + ctx->reply.body.len;
+
+	if (val && val->s && val->len) {
+		if (id && id->s && id->len) {
+			for(i=0;i<ctx->struc_depth;i++)
+				XHTTP_RPC_COPY(p,XHTTP_RPC_NODE_INDENT);
+			if ((int)(p-buf)+id->len>max_page_len) {
+				goto error;
+			}
+			memcpy(p, id->s, id->len);
+			p += id->len;
+			XHTTP_RPC_COPY(p,XHTTP_RPC_SEMICOLON);
+		}
+		if ((int)(p-buf)+val->len>max_page_len) {
+			goto error;
+		}
+		memcpy(p, val->s, val->len);
+		p += val->len;
+		XHTTP_RPC_COPY(p,XHTTP_RPC_BREAK);
+	} else {
+		if (id && id->s && id->len) {
+			for(i=0;i<ctx->struc_depth;i++)
+				XHTTP_RPC_COPY(p,XHTTP_RPC_NODE_INDENT);
+			if ((int)(p-buf)+id->len>max_page_len) {
+				goto error;
+			}
+			memcpy(p, id->s, id->len);
+			p += id->len;
+			XHTTP_RPC_COPY(p,XHTTP_RPC_SEMICOLON);
+			XHTTP_RPC_COPY(p,XHTTP_RPC_BREAK);
+		}
+	}
+	ctx->reply.body.len = p - ctx->reply.body.s;
+
+	return 0;
+error:
+	LM_ERR("buffer 2 small\n");
+	ctx->reply.body.len = p - ctx->reply.body.s;
+	return -1;
+}
+
+
+int xhttp_rpc_insert_break(rpc_ctx_t *ctx)
+{
+	char *p = ctx->reply.body.s + ctx->reply.body.len;;
+	char *buf = ctx->reply.buf.s;
+	int max_page_len = ctx->reply.buf.len;
+
+	XHTTP_RPC_COPY(p,XHTTP_RPC_BREAK);
+
+	ctx->reply.body.len = p - ctx->reply.body.s;
+	return 0;
+error:
+	LM_ERR("buffer 2 small\n");
+	ctx->reply.body.len = p - ctx->reply.body.s;
+	return -1;
+}
+
+int xhttp_rpc_build_page(rpc_ctx_t *ctx)
+{
+	char *p;
+	char *buf = ctx->reply.buf.s;
+	int max_page_len = ctx->reply.buf.len;
+
+	if (ctx->reply.body.len==0)
+		if (0!=xhttp_rpc_build_content(ctx, NULL, NULL))
+			return -1;
+
+	p = ctx->reply.body.s + ctx->reply.body.len;
+
+	if (ctx->arg_received) {
+		XHTTP_RPC_COPY_5(p,XHTTP_RPC_CODE_2,
+				XHTTP_RPC_Response_Menu_Cmd_td_4d,
+				XHTTP_RPC_Response_Menu_Cmd_tr_2,
+				XHTTP_RPC_Response_Menu_Cmd_Table_2,
+				XHTTP_RPC_Response_Foot);
+		ctx->reply.body.len = p - ctx->reply.body.s;
+	}
+
+	return 0;
+error:
+	LM_ERR("buffer 2 small\n");
+	ctx->reply.body.len = p - ctx->reply.body.s;
+	return -1;
+}

+ 37 - 0
modules/xhttp_rpc/xhttp_rpc_fnc.h

@@ -0,0 +1,37 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2011 VoIP Embedded, Inc.
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * 2011-11-11  initial version (osas)
+ */
+
+
+#ifndef _XHTTP_RPC_FNC_H
+#define _XHTTP_RPC_FNC_H
+
+
+int xhttp_rpc_parse_url(str *url, int *mod, int *cmd, str *arg);
+void xhttp_rpc_get_next_arg(rpc_ctx_t* ctx, str *arg);
+int xhttp_rpc_build_content(rpc_ctx_t *ctx, str *val, str *id);
+int xhttp_rpc_insert_break(rpc_ctx_t *ctx);
+int xhttp_rpc_build_page(rpc_ctx_t *ctx);
+
+#endif
+