/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * soup-xmlrpc.c: XML-RPC parser/generator
 *
 * Copyright (C) 2007 Red Hat, Inc.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string.h>

#include <libxml/tree.h>

#include "soup-xmlrpc-old.h"
#include "soup.h"

/**
 * SECTION:soup-xmlrpc
 * @short_description: XML-RPC support
 *
 **/

/* This whole file is deprecated and replaced by soup-xmlrpc.c */
G_GNUC_BEGIN_IGNORE_DEPRECATIONS

static xmlNode *find_real_node (xmlNode *node);

static gboolean insert_value (xmlNode *parent, GValue *value);

static gboolean
insert_value (xmlNode *parent, GValue *value)
{
	GType type = G_VALUE_TYPE (value);
	xmlNode *xvalue;
	char buf[128];

	xvalue = xmlNewChild (parent, NULL, (const xmlChar *)"value", NULL);

	if (type == G_TYPE_INT) {
		g_snprintf (buf, sizeof (buf), "%d", g_value_get_int (value));
		xmlNewChild (xvalue, NULL,
			     (const xmlChar *)"int",
			     (const xmlChar *)buf);
	} else if (type == G_TYPE_BOOLEAN) {
		g_snprintf (buf, sizeof (buf), "%d", g_value_get_boolean (value));
		xmlNewChild (xvalue, NULL,
			     (const xmlChar *)"boolean",
			     (const xmlChar *)buf);
	} else if (type == G_TYPE_STRING) {
		xmlNewTextChild (xvalue, NULL,
				 (const xmlChar *)"string",
				 (const xmlChar *)g_value_get_string (value));
	} else if (type == G_TYPE_DOUBLE) {
		g_ascii_dtostr (buf, sizeof (buf), g_value_get_double (value));
		xmlNewChild (xvalue, NULL,
			     (const xmlChar *)"double",
			     (const xmlChar *)buf);
	} else if (type == SOUP_TYPE_DATE) {
		SoupDate *date = g_value_get_boxed (value);
		char *timestamp = soup_date_to_string (date, SOUP_DATE_ISO8601_XMLRPC);
		xmlNewChild (xvalue, NULL,
			     (const xmlChar *)"dateTime.iso8601",
			     (const xmlChar *)timestamp);
		g_free (timestamp);
	} else if (type == SOUP_TYPE_BYTE_ARRAY) {
		GByteArray *ba = g_value_get_boxed (value);
		char *encoded;

		encoded = g_base64_encode (ba->data, ba->len);
		xmlNewChild (xvalue, NULL,
			     (const xmlChar *)"base64",
			     (const xmlChar *)encoded);
		g_free (encoded);
	} else if (type == G_TYPE_HASH_TABLE) {
		GHashTable *hash = g_value_get_boxed (value);
		GHashTableIter iter;
		gpointer mname, mvalue;
		xmlNode *struct_node, *member;

		struct_node = xmlNewChild (xvalue, NULL,
					   (const xmlChar *)"struct", NULL);

		g_hash_table_iter_init (&iter, hash);

		while (g_hash_table_iter_next (&iter, &mname, &mvalue)) {
			member = xmlNewChild (struct_node, NULL,
					      (const xmlChar *)"member", NULL);
			xmlNewTextChild (member, NULL,
					 (const xmlChar *)"name",
					 (const xmlChar *)mname);
			if (!insert_value (member, mvalue)) {
				xmlFreeNode (struct_node);
				struct_node = NULL;
				break;
			}
		}

		if (!struct_node)
			return FALSE;
	} else if (type == G_TYPE_VALUE_ARRAY) {
		GValueArray *va = g_value_get_boxed (value);
		xmlNode *node;
		guint i;

		node = xmlNewChild (xvalue, NULL,
				    (const xmlChar *)"array", NULL);
		node = xmlNewChild (node, NULL,
				    (const xmlChar *)"data", NULL);
		for (i = 0; i < va->n_values; i++) {
			if (!insert_value (node, &va->values[i]))
				return FALSE;
		}
	} else
		return FALSE;

	return TRUE;
}

/**
 * soup_xmlrpc_build_method_call:
 * @method_name: the name of the XML-RPC method
 * @params: (array length=n_params): arguments to @method
 * @n_params: length of @params
 *
 * This creates an XML-RPC methodCall and returns it as a string.
 * This is the low-level method that soup_xmlrpc_request_new() is
 * built on.
 *
 * @params is an array of #GValue representing the parameters to
 * @method. (It is *not* a #GValueArray, although if you have a
 * #GValueArray, you can just pass its <literal>values</literal>f and
 * <literal>n_values</literal> fields.)
 *
 * The correspondence between glib types and XML-RPC types is:
 *
 *   int: #int (%G_TYPE_INT)
 *   boolean: #gboolean (%G_TYPE_BOOLEAN)
 *   string: #char* (%G_TYPE_STRING)
 *   double: #double (%G_TYPE_DOUBLE)
 *   datetime.iso8601: #SoupDate (%SOUP_TYPE_DATE)
 *   base64: #GByteArray (%SOUP_TYPE_BYTE_ARRAY)
 *   struct: #GHashTable (%G_TYPE_HASH_TABLE)
 *   array: #GValueArray (%G_TYPE_VALUE_ARRAY)
 *
 * For structs, use a #GHashTable that maps strings to #GValue;
 * soup_value_hash_new() and related methods can help with this.
 *
 * Return value: (nullable): the text of the methodCall, or %NULL on
 * error
 *
 * Deprecated: Use soup_xmlrpc_build_request() instead.
 **/
char *
soup_xmlrpc_build_method_call (const char *method_name,
			       GValue *params, int n_params)
{
	xmlDoc *doc;
	xmlNode *node, *param;
	xmlChar *xmlbody;
	int i, len;
	char *body;

	doc = xmlNewDoc ((const xmlChar *)"1.0");
	doc->standalone = FALSE;
	doc->encoding = xmlCharStrdup ("UTF-8");

	node = xmlNewDocNode (doc, NULL, (const xmlChar *)"methodCall", NULL);
	xmlDocSetRootElement (doc, node);
	xmlNewChild (node, NULL, (const xmlChar *)"methodName",
		     (const xmlChar *)method_name);

	node = xmlNewChild (node, NULL, (const xmlChar *)"params", NULL);
	for (i = 0; i < n_params; i++) {
		param  = xmlNewChild (node, NULL,
				      (const xmlChar *)"param", NULL);
		if (!insert_value (param, &params[i])) {
			xmlFreeDoc (doc);
			return NULL;
		}
	}

	xmlDocDumpMemory (doc, &xmlbody, &len);
	body = g_strndup ((char *)xmlbody, len);
	xmlFree (xmlbody);
	xmlFreeDoc (doc);
	return body;
}

static SoupMessage *
soup_xmlrpc_request_newv (const char *uri, const char *method_name, va_list args)
{
	SoupMessage *msg;
	GValueArray *params;
	char *body;

	params = soup_value_array_from_args (args);
	if (!params)
		return NULL;

	body = soup_xmlrpc_build_method_call (method_name, params->values,
					      params->n_values);
	g_value_array_free (params);

	if (!body)
		return NULL;

	msg = soup_message_new ("POST", uri);
	soup_message_set_request (msg, "text/xml", SOUP_MEMORY_TAKE,
				  body, strlen (body));
	return msg;
}

/**
 * soup_xmlrpc_request_new:
 * @uri: URI of the XML-RPC service
 * @method_name: the name of the XML-RPC method to invoke at @uri
 * @...: parameters for @method
 *
 * Creates an XML-RPC methodCall and returns a #SoupMessage, ready
 * to send, for that method call.
 *
 * The parameters are passed as type/value pairs; ie, first a #GType,
 * and then a value of the appropriate type, finally terminated by
 * %G_TYPE_INVALID.
 *
 * Return value: (transfer full): a #SoupMessage encoding the
 * indicated XML-RPC request.
 *
 * Deprecated: Use soup_xmlrpc_message_new() instead.
 **/
SoupMessage *
soup_xmlrpc_request_new (const char *uri, const char *method_name, ...)
{
	SoupMessage *msg;
	va_list args;

	va_start (args, method_name);
	msg = soup_xmlrpc_request_newv (uri, method_name, args);
	va_end (args);
	return msg;
}

/**
 * soup_xmlrpc_build_method_response:
 * @value: the return value
 *
 * This creates a (successful) XML-RPC methodResponse and returns it
 * as a string. To create a fault response, use
 * soup_xmlrpc_build_fault().
 *
 * The glib type to XML-RPC type mapping is as with
 * soup_xmlrpc_build_method_call(), qv.
 *
 * Return value: (nullable): the text of the methodResponse, or %NULL
 * on error
 *
 * Deprecated: Use soup_xmlrpc_build_response() instead.
 **/
char *
soup_xmlrpc_build_method_response (GValue *value)
{
	xmlDoc *doc;
	xmlNode *node;
	xmlChar *xmlbody;
	char *body;
	int len;

	doc = xmlNewDoc ((const xmlChar *)"1.0");
	doc->standalone = FALSE;
	doc->encoding = xmlCharStrdup ("UTF-8");

	node = xmlNewDocNode (doc, NULL,
			      (const xmlChar *)"methodResponse", NULL);
	xmlDocSetRootElement (doc, node);

	node = xmlNewChild (node, NULL, (const xmlChar *)"params", NULL);
	node = xmlNewChild (node, NULL, (const xmlChar *)"param", NULL);
	if (!insert_value (node, value)) {
		xmlFreeDoc (doc);
		return NULL;
	}

	xmlDocDumpMemory (doc, &xmlbody, &len);
	body = g_strndup ((char *)xmlbody, len);
	xmlFree (xmlbody);
	xmlFreeDoc (doc);
	return body;
}

/**
 * soup_xmlrpc_set_response:
 * @msg: an XML-RPC request
 * @type: the type of the response value
 * @...: the response value
 *
 * Sets the status code and response body of @msg to indicate a
 * successful XML-RPC call, with a return value given by @type and the
 * following varargs argument, of the type indicated by @type.
 *
 * Deprecated: Use soup_xmlrpc_message_set_response() instead.
 **/
void
soup_xmlrpc_set_response (SoupMessage *msg, GType type, ...)
{
	va_list args;
	GValue value;
	char *body;

	va_start (args, type);
	SOUP_VALUE_SETV (&value, type, args);
	va_end (args);

	body = soup_xmlrpc_build_method_response (&value);
	g_value_unset (&value);
	soup_message_set_status (msg, SOUP_STATUS_OK);
	soup_message_set_response (msg, "text/xml", SOUP_MEMORY_TAKE,
				   body, strlen (body));
}

char *soup_xmlrpc_build_faultv (int         fault_code,
				const char *fault_format,
				va_list     args) G_GNUC_PRINTF (2, 0);

/**
 * soup_xmlrpc_set_fault:
 * @msg: an XML-RPC request
 * @fault_code: the fault code
 * @fault_format: a printf()-style format string
 * @...: the parameters to @fault_format
 *
 * Sets the status code and response body of @msg to indicate an
 * unsuccessful XML-RPC call, with the error described by @fault_code
 * and @fault_format.
 *
 * Deprecated: Use soup_xmlrpc_message_set_fault() instead.
 **/
void
soup_xmlrpc_set_fault (SoupMessage *msg, int fault_code,
		       const char *fault_format, ...)
{
	va_list args;
	char *body;

	va_start (args, fault_format);
	body = soup_xmlrpc_build_faultv (fault_code, fault_format, args);
	va_end (args);

	soup_message_set_status (msg, SOUP_STATUS_OK);
	soup_message_set_response (msg, "text/xml", SOUP_MEMORY_TAKE,
				   body, strlen (body));
}



static gboolean
parse_value (xmlNode *xmlvalue, GValue *value)
{
	xmlNode *typenode;
	const char *typename;
	xmlChar *content;

	memset (value, 0, sizeof (GValue));

	typenode = find_real_node (xmlvalue->children);
	if (!typenode) {
		/* If no type node, it's a string */
		content = xmlNodeGetContent (typenode);
		g_value_init (value, G_TYPE_STRING);
		g_value_set_string (value, (char *)content);
		xmlFree (content);
		return TRUE;
	}

	typename = (const char *)typenode->name;

	if (!strcmp (typename, "i4") || !strcmp (typename, "int")) {
		content = xmlNodeGetContent (typenode);
		g_value_init (value, G_TYPE_INT);
		g_value_set_int (value, atoi ((char *)content));
		xmlFree (content);
	} else if (!strcmp (typename, "boolean")) {
		content = xmlNodeGetContent (typenode);
		g_value_init (value, G_TYPE_BOOLEAN);
		g_value_set_boolean (value, atoi ((char *)content));
		xmlFree (content);
	} else if (!strcmp (typename, "string")) {
		content = xmlNodeGetContent (typenode);
		g_value_init (value, G_TYPE_STRING);
		g_value_set_string (value, (char *)content);
		xmlFree (content);
	} else if (!strcmp (typename, "double")) {
		content = xmlNodeGetContent (typenode);
		g_value_init (value, G_TYPE_DOUBLE);
		g_value_set_double (value, g_ascii_strtod ((char *)content, NULL));
		xmlFree (content);
	} else if (!strcmp (typename, "dateTime.iso8601")) {
		content = xmlNodeGetContent (typenode);
		g_value_init (value, SOUP_TYPE_DATE);
		g_value_take_boxed (value, soup_date_new_from_string ((char *)content));
		xmlFree (content);
	} else if (!strcmp (typename, "base64")) {
		GByteArray *ba;
		guchar *decoded;
		gsize len;

		content = xmlNodeGetContent (typenode);
		decoded = g_base64_decode ((char *)content, &len);
		ba = g_byte_array_sized_new (len);
		g_byte_array_append (ba, decoded, len);
		g_free (decoded);
		xmlFree (content);
		g_value_init (value, SOUP_TYPE_BYTE_ARRAY);
		g_value_take_boxed (value, ba);
	} else if (!strcmp (typename, "struct")) {
		xmlNode *member, *child, *mname, *mxval;
		GHashTable *hash;
		GValue mgval;
		
		hash = soup_value_hash_new ();
		for (member = find_real_node (typenode->children);
		     member;
		     member = find_real_node (member->next)) {
			if (strcmp ((const char *)member->name, "member") != 0) {
				g_hash_table_destroy (hash);
				return FALSE;
			}
			mname = mxval = NULL;
			memset (&mgval, 0, sizeof (mgval));

			for (child = find_real_node (member->children);
			     child;
			     child = find_real_node (child->next)) {
				if (!strcmp ((const char *)child->name, "name"))
					mname = child;
				else if (!strcmp ((const char *)child->name, "value"))
					mxval = child;
				else
					break;
			}

			if (!mname || !mxval || !parse_value (mxval, &mgval)) {
				g_hash_table_destroy (hash);
				return FALSE;
			}

			content = xmlNodeGetContent (mname);
			soup_value_hash_insert_value (hash, (char *)content, &mgval);
			xmlFree (content);
			g_value_unset (&mgval);
		}
		g_value_init (value, G_TYPE_HASH_TABLE);
		g_value_take_boxed (value, hash);
	} else if (!strcmp (typename, "array")) {
		xmlNode *data, *xval;
		GValueArray *array;
		GValue gval;

		data = find_real_node (typenode->children);
		if (!data || strcmp ((const char *)data->name, "data") != 0)
			return FALSE;

		array = g_value_array_new (1);
		for (xval = find_real_node (data->children);
		     xval;
		     xval = find_real_node (xval->next)) {
			memset (&gval, 0, sizeof (gval));
			if (strcmp ((const char *)xval->name, "value") != 0 ||
			    !parse_value (xval, &gval)) {
				g_value_array_free (array);
				return FALSE;
			}

			g_value_array_append (array, &gval);
			g_value_unset (&gval);
		}
		g_value_init (value, G_TYPE_VALUE_ARRAY);
		g_value_take_boxed (value, array);
	} else
		return FALSE;

	return TRUE;
}

/**
 * soup_xmlrpc_parse_method_call:
 * @method_call: the XML-RPC methodCall string
 * @length: the length of @method_call, or -1 if it is NUL-terminated
 * @method_name: (out): on return, the methodName from @method_call
 * @params: (out): on return, the parameters from @method_call
 *
 * Parses @method_call to get the name and parameters, and returns the
 * parameter values in a #GValueArray; see also
 * soup_xmlrpc_extract_method_call(), which is more convenient if you
 * know in advance what the types of the parameters will be.
 *
 * Return value: success or failure.
 *
 * Deprecated: Use soup_xmlrpc_parse_request_full() instead.
 **/
gboolean
soup_xmlrpc_parse_method_call (const char *method_call, int length,
			       char **method_name, GValueArray **params)
{
	xmlDoc *doc;
	xmlNode *node, *param, *xval;
	xmlChar *xmlMethodName = NULL;
	gboolean success = FALSE;
	GValue value;

	doc = xmlParseMemory (method_call,
			      length == -1 ? strlen (method_call) : length);
	if (!doc)
		return FALSE;

	node = xmlDocGetRootElement (doc);
	if (!node || strcmp ((const char *)node->name, "methodCall") != 0)
		goto fail;

	node = find_real_node (node->children);
	if (!node || strcmp ((const char *)node->name, "methodName") != 0)
		goto fail;
	xmlMethodName = xmlNodeGetContent (node);

	node = find_real_node (node->next);
	if (node) {
		if (strcmp ((const char *)node->name, "params") != 0)
			goto fail;

		*params = soup_value_array_new ();
		param = find_real_node (node->children);
		while (param && !strcmp ((const char *)param->name, "param")) {
			xval = find_real_node (param->children);
			if (!xval || strcmp ((const char *)xval->name, "value") != 0 ||
			    !parse_value (xval, &value)) {
				g_value_array_free (*params);
				goto fail;
			}
			g_value_array_append (*params, &value);
			g_value_unset (&value);

			param = find_real_node (param->next);
		}
	} else
		*params = soup_value_array_new ();

	success = TRUE;
	*method_name = g_strdup ((char *)xmlMethodName);

fail:
	xmlFreeDoc (doc);
	if (xmlMethodName)
		xmlFree (xmlMethodName);
	return success;
}

/**
 * soup_xmlrpc_extract_method_call:
 * @method_call: the XML-RPC methodCall string
 * @length: the length of @method_call, or -1 if it is NUL-terminated
 * @method_name: (out): on return, the methodName from @method_call
 * @...: return types and locations for parameters
 *
 * Parses @method_call to get the name and parameters, and puts
 * the parameters into variables of the appropriate types.
 *
 * The parameters are handled similarly to
 * @soup_xmlrpc_build_method_call, with pairs of types and values,
 * terminated by %G_TYPE_INVALID, except that values are pointers to
 * variables of the indicated type, rather than values of the type.
 *
 * See also soup_xmlrpc_parse_method_call(), which can be used if
 * you don't know the types of the parameters.
 *
 * Return value: success or failure.
 *
 * Deprecated: Use soup_xmlrpc_parse_request_full() instead.
 **/
gboolean
soup_xmlrpc_extract_method_call (const char *method_call, int length,
				 char **method_name, ...)
{
	GValueArray *params;
	gboolean success;
	va_list args;

	if (!soup_xmlrpc_parse_method_call (method_call, length,
					    method_name, &params))
		return FALSE;

	va_start (args, method_name);
	success = soup_value_array_to_args (params, args);
	va_end (args);

	g_value_array_free (params);

	return success;
}

/**
 * soup_xmlrpc_parse_method_response:
 * @method_response: the XML-RPC methodResponse string
 * @length: the length of @method_response, or -1 if it is NUL-terminated
 * @value: (out): on return, the return value from @method_call
 * @error: error return value
 *
 * Parses @method_response and returns the return value in @value. If
 * @method_response is a fault, @value will be unchanged, and @error
 * will be set to an error of type %SOUP_XMLRPC_FAULT, with the error
 * #code containing the fault code, and the error #message containing
 * the fault string. (If @method_response cannot be parsed at all,
 * soup_xmlrpc_parse_method_response() will return %FALSE, but @error
 * will be unset.)
 *
 * Return value: %TRUE if a return value was parsed, %FALSE if the
 * response could not be parsed, or contained a fault.
 *
 * Deprecated: Use soup_xmlrpc_parse_response() instead.
 **/
gboolean
soup_xmlrpc_parse_method_response (const char *method_response, int length,
				   GValue *value, GError **error)
{
	xmlDoc *doc;
	xmlNode *node;
	gboolean success = FALSE;

	doc = xmlParseMemory (method_response,
			      length == -1 ? strlen (method_response) : length);
	if (!doc)
		return FALSE;

	node = xmlDocGetRootElement (doc);
	if (!node || strcmp ((const char *)node->name, "methodResponse") != 0)
		goto fail;

	node = find_real_node (node->children);
	if (!node)
		goto fail;

	if (!strcmp ((const char *)node->name, "fault") && error) {
		int fault_code;
		char *fault_string;
		GValue fault_val;
		GHashTable *fault_hash;

		node = find_real_node (node->children);
		if (!node || strcmp ((const char *)node->name, "value") != 0)
			goto fail;
		if (!parse_value (node, &fault_val))
			goto fail;
		if (!G_VALUE_HOLDS (&fault_val, G_TYPE_HASH_TABLE)) {
			g_value_unset (&fault_val);
			goto fail;
		}
		fault_hash = g_value_get_boxed (&fault_val);
		if (!soup_value_hash_lookup (fault_hash, "faultCode",
					     G_TYPE_INT, &fault_code) ||
		    !soup_value_hash_lookup (fault_hash, "faultString",
					     G_TYPE_STRING, &fault_string)) {
			g_value_unset (&fault_val);
			goto fail;
		}

		g_set_error (error, SOUP_XMLRPC_FAULT,
			     fault_code, "%s", fault_string);
		g_value_unset (&fault_val);
	} else if (!strcmp ((const char *)node->name, "params")) {
		node = find_real_node (node->children);
		if (!node || strcmp ((const char *)node->name, "param") != 0)
			goto fail;
		node = find_real_node (node->children);
		if (!node || strcmp ((const char *)node->name, "value") != 0)
			goto fail;
		if (!parse_value (node, value))
			goto fail;
		success = TRUE;
	}

fail:
	xmlFreeDoc (doc);
	return success;
}

/**
 * soup_xmlrpc_extract_method_response:
 * @method_response: the XML-RPC methodResponse string
 * @length: the length of @method_response, or -1 if it is NUL-terminated
 * @error: error return value
 * @type: the expected type of the return value
 * @...: location for return value
 *
 * Parses @method_response and extracts the return value into
 * a variable of the correct type.
 *
 * If @method_response is a fault, the return value will be unset,
 * and @error will be set to an error of type %SOUP_XMLRPC_FAULT, with
 * the error #code containing the fault code, and the error #message
 * containing the fault string. (If @method_response cannot be parsed
 * at all, soup_xmlrpc_extract_method_response() will return %FALSE,
 * but @error will be unset.)
 *
 * Return value: %TRUE if a return value was parsed, %FALSE if the
 * response was of the wrong type, or contained a fault.
 *
 * Deprecated: Use soup_xmlrpc_parse_response() instead.
 **/
gboolean
soup_xmlrpc_extract_method_response (const char *method_response, int length,
				     GError **error, GType type, ...)
{
	GValue value;
	va_list args;

	if (!soup_xmlrpc_parse_method_response (method_response, length,
						&value, error))
		return FALSE;
	if (!G_VALUE_HOLDS (&value, type))
		return FALSE;

	va_start (args, type);
	SOUP_VALUE_GETV (&value, type, args);
	va_end (args);

	return TRUE;
}

static xmlNode *
find_real_node (xmlNode *node)
{
	while (node && (node->type == XML_COMMENT_NODE ||
			xmlIsBlankNode (node)))
		node = node->next;
	return node;
}

G_GNUC_END_IGNORE_DEPRECATIONS
