[libsoup/wip/xclaesse/xmlrpc: 8/10] xmlrpc: rename files



commit c4a18e4a875741b3fca49de2174765a63ab06a76
Author: Xavier Claessens <xavier claessens collabora com>
Date:   Mon Jun 22 16:45:16 2015 -0400

    xmlrpc: rename files
    
    https://bugzilla.gnome.org/show_bug.cgi?id=746495

 libsoup/Makefile.am           |    4 +-
 libsoup/soup-xmlrpc-gvalue.c  |  887 +++++++++++++++++++++
 libsoup/soup-xmlrpc-gvalue.h  |   85 ++
 libsoup/soup-xmlrpc-variant.c | 1329 --------------------------------
 libsoup/soup-xmlrpc-variant.h |   62 --
 libsoup/soup-xmlrpc.c         | 1698 ++++++++++++++++++++++++++---------------
 libsoup/soup-xmlrpc.h         |  113 ++--
 libsoup/soup.h                |    2 +-
 tests/Makefile.am             |    2 +-
 tests/xmlrpc-gvalue-test.c    |  513 +++++++++++++
 tests/xmlrpc-test.c           |  678 ++++++-----------
 tests/xmlrpc-variant-test.c   |  269 -------
 12 files changed, 2821 insertions(+), 2821 deletions(-)
---
diff --git a/libsoup/Makefile.am b/libsoup/Makefile.am
index f864769..fea3e6c 100644
--- a/libsoup/Makefile.am
+++ b/libsoup/Makefile.am
@@ -71,7 +71,7 @@ soup_headers =                        \
        soup-websocket.h        \
        soup-websocket-connection.h     \
        soup-xmlrpc.h           \
-       soup-xmlrpc-variant.h
+       soup-xmlrpc-gvalue.h
 
 libsoupinclude_HEADERS =       \
        $(soup_headers)         \
@@ -194,7 +194,7 @@ libsoup_2_4_la_SOURCES =            \
        soup-websocket.c                \
        soup-websocket-connection.c     \
        soup-xmlrpc.c                   \
-       soup-xmlrpc-variant.c
+       soup-xmlrpc-gvalue.c
 
 # TLD rules
 EXTRA_DIST += tld-parser.py
diff --git a/libsoup/soup-xmlrpc-gvalue.c b/libsoup/soup-xmlrpc-gvalue.c
new file mode 100644
index 0000000..6e742c0
--- /dev/null
+++ b/libsoup/soup-xmlrpc-gvalue.c
@@ -0,0 +1,887 @@
+/* -*- 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-gvalue.h"
+#include "soup.h"
+
+/**
+ * SECTION:soup-xmlrpc
+ * @short_description: XML-RPC support
+ *
+ **/
+
+/* This whole file is pretty much deprecated and replaced by
+ * soup-xmlrpc-variant.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) {
+               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) {
+               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;
+               int 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.
+ *
+ * Deprecated: Use soup_xmlrpc_build_request() instead.
+ * Return value: (nullable): the text of the methodCall, or %NULL on
+ * error
+ **/
+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.
+ *
+ * Deprecated: Use soup_xmlrpc_message_new() instead.
+ * Return value: (transfer full): a #SoupMessage encoding the
+ * indicated XML-RPC request.
+ **/
+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.
+ *
+ * Deprecated: Use soup_xmlrpc_build_response() instead.
+ * Return value: (nullable): the text of the methodResponse, or %NULL
+ * on error
+ **/
+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;
+}
+
+static char *
+soup_xmlrpc_build_faultv (int         fault_code,
+                          const char *fault_format,
+                          va_list     args) G_GNUC_PRINTF (2, 0);
+
+static char *
+soup_xmlrpc_build_faultv (int fault_code, const char *fault_format, va_list args)
+{
+       xmlDoc *doc;
+       xmlNode *node, *member;
+       GValue value;
+       xmlChar *xmlbody;
+       char *fault_string, *body;
+       int len;
+
+       fault_string = g_strdup_vprintf (fault_format, args);
+
+       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 *)"fault", NULL);
+       node = xmlNewChild (node, NULL, (const xmlChar *)"value", NULL);
+       node = xmlNewChild (node, NULL, (const xmlChar *)"struct", NULL);
+
+       memset (&value, 0, sizeof (value));
+
+       member = xmlNewChild (node, NULL, (const xmlChar *)"member", NULL);
+       xmlNewChild (member, NULL,
+                    (const xmlChar *)"name", (const xmlChar *)"faultCode");
+       g_value_init (&value, G_TYPE_INT);
+       g_value_set_int (&value, fault_code);
+       insert_value (member, &value);
+       g_value_unset (&value);
+
+       member = xmlNewChild (node, NULL, (const xmlChar *)"member", NULL);
+       xmlNewChild (member, NULL,
+                    (const xmlChar *)"name", (const xmlChar *)"faultString");
+       g_value_init (&value, G_TYPE_STRING);
+       g_value_take_string (&value, fault_string);
+       insert_value (member, &value);
+       g_value_unset (&value);
+
+       xmlDocDumpMemory (doc, &xmlbody, &len);
+       body = g_strndup ((char *)xmlbody, len);
+       xmlFree (xmlbody);
+       xmlFreeDoc (doc);
+
+       return body;
+}
+
+/**
+ * soup_xmlrpc_build_fault:
+ * @fault_code: the fault code
+ * @fault_format: a printf()-style format string
+ * @...: the parameters to @fault_format
+ *
+ * This creates an XML-RPC fault response and returns it as a string.
+ * (To create a successful response, use
+ * soup_xmlrpc_build_method_response().)
+ *
+ * Return value: the text of the fault
+ **/
+char *
+soup_xmlrpc_build_fault (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);
+       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));
+}
+
+/**
+ * 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));
+}
+
+/**
+ * soup_xmlrpc_message_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.
+ **/
+void
+soup_xmlrpc_message_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.
+ *
+ * Deprecated: Use soup_xmlrpc_parse_request_full() instead.
+ * Return value: success or failure.
+ **/
+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.
+ *
+ * Deprecated: Use soup_xmlrpc_parse_request_full() instead.
+ * Return value: success or failure.
+ **/
+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.)
+ *
+ * Deprecated: Use soup_xmlrpc_parse_response() instead.
+ * Return value: %TRUE if a return value was parsed, %FALSE if the
+ * response could not be parsed, or contained a fault.
+ **/
+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.)
+ *
+ * Deprecated: Use soup_xmlrpc_parse_response() instead.
+ * Return value: %TRUE if a return value was parsed, %FALSE if the
+ * response was of the wrong type, or contained a fault.
+ **/
+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;
+}
+
+
+GQuark
+soup_xmlrpc_error_quark (void)
+{
+       static GQuark error;
+       if (!error)
+               error = g_quark_from_static_string ("soup_xmlrpc_error_quark");
+       return error;
+}
+
+/**
+ * SOUP_XMLRPC_FAULT:
+ *
+ * A #GError domain representing an XML-RPC fault code. Used with
+ * #SoupXMLRPCFault (although servers may also return fault codes not
+ * in that enumeration).
+ */
+
+/**
+ * SoupXMLRPCFault:
+ * @SOUP_XMLRPC_FAULT_PARSE_ERROR_NOT_WELL_FORMED: request was not
+ *   well-formed
+ * @SOUP_XMLRPC_FAULT_PARSE_ERROR_UNSUPPORTED_ENCODING: request was in
+ *   an unsupported encoding
+ * @SOUP_XMLRPC_FAULT_PARSE_ERROR_INVALID_CHARACTER_FOR_ENCODING:
+ *   request contained an invalid character
+ * @SOUP_XMLRPC_FAULT_SERVER_ERROR_INVALID_XML_RPC: request was not
+ *   valid XML-RPC
+ * @SOUP_XMLRPC_FAULT_SERVER_ERROR_REQUESTED_METHOD_NOT_FOUND: method
+ *   not found
+ * @SOUP_XMLRPC_FAULT_SERVER_ERROR_INVALID_METHOD_PARAMETERS: invalid
+ *   parameters
+ * @SOUP_XMLRPC_FAULT_SERVER_ERROR_INTERNAL_XML_RPC_ERROR: internal
+ *   error
+ * @SOUP_XMLRPC_FAULT_APPLICATION_ERROR: start of reserved range for
+ *   application error codes
+ * @SOUP_XMLRPC_FAULT_SYSTEM_ERROR: start of reserved range for
+ *   system error codes
+ * @SOUP_XMLRPC_FAULT_TRANSPORT_ERROR: start of reserved range for
+ *   transport error codes
+ *
+ * Pre-defined XML-RPC fault codes from <ulink
+ * 
url="http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php";>http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php</ulink>.
+ * These are an extension, not part of the XML-RPC spec; you can't
+ * assume servers will use them.
+ */
+
+GQuark
+soup_xmlrpc_fault_quark (void)
+{
+       static GQuark error;
+       if (!error)
+               error = g_quark_from_static_string ("soup_xmlrpc_fault_quark");
+       return error;
+}
+
+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
diff --git a/libsoup/soup-xmlrpc-gvalue.h b/libsoup/soup-xmlrpc-gvalue.h
new file mode 100644
index 0000000..d28bea6
--- /dev/null
+++ b/libsoup/soup-xmlrpc-gvalue.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Red Hat, Inc.
+ */
+
+#ifndef SOUP_XMLRPC_GVALUE_H
+#define SOUP_XMLRPC_GVALUE_H 1
+
+#include <libsoup/soup-types.h>
+
+G_BEGIN_DECLS
+
+/* XML-RPC client */
+SOUP_DEPRECATED_IN_2_52_FOR(soup_xmlrpc_build_request)
+char        *soup_xmlrpc_build_method_call       (const char   *method_name,
+                                                 GValue       *params,
+                                                 int           n_params);
+SOUP_DEPRECATED_IN_2_52_FOR(soup_xmlrpc_message_new)
+SoupMessage *soup_xmlrpc_request_new             (const char   *uri,
+                                                 const char   *method_name,
+                                                 ...);
+SOUP_DEPRECATED_IN_2_52_FOR(soup_xmlrpc_parse_response)
+gboolean     soup_xmlrpc_parse_method_response   (const char   *method_response,
+                                                 int           length,
+                                                 GValue       *value,
+                                                 GError      **error);
+SOUP_DEPRECATED_IN_2_52_FOR(soup_xmlrpc_parse_response)
+gboolean     soup_xmlrpc_extract_method_response (const char   *method_response,
+                                                 int           length,
+                                                 GError      **error,
+                                                 GType         type,
+                                                 ...);
+
+/* XML-RPC server */
+SOUP_DEPRECATED_IN_2_52_FOR(soup_xmlrpc_parse_request_full)
+gboolean     soup_xmlrpc_parse_method_call       (const char   *method_call,
+                                                 int           length,
+                                                 char        **method_name,
+                                                 GValueArray **params);
+SOUP_DEPRECATED_IN_2_52_FOR(soup_xmlrpc_parse_request_full)
+gboolean     soup_xmlrpc_extract_method_call     (const char   *method_call,
+                                                 int           length,
+                                                 char        **method_name,
+                                                 ...);
+SOUP_DEPRECATED_IN_2_52_FOR(soup_xmlrpc_build_response)
+char        *soup_xmlrpc_build_method_response   (GValue       *value);
+SOUP_DEPRECATED_IN_2_52_FOR(soup_xmlrpc_message_set_response)
+void         soup_xmlrpc_set_response            (SoupMessage  *msg,
+                                                 GType         type,
+                                                 ...);
+SOUP_DEPRECATED_IN_2_52_FOR(soup_xmlrpc_message_set_fault)
+void         soup_xmlrpc_set_fault               (SoupMessage  *msg,
+                                                 int           fault_code,
+                                                 const char   *fault_format,
+                                                 ...) G_GNUC_PRINTF (3, 4);
+
+
+/* Errors */
+#define SOUP_XMLRPC_ERROR soup_xmlrpc_error_quark()
+GQuark soup_xmlrpc_error_quark (void);
+
+typedef enum {
+       SOUP_XMLRPC_ERROR_ARGUMENTS,
+       SOUP_XMLRPC_ERROR_RETVAL
+} SoupXMLRPCError;
+
+#define SOUP_XMLRPC_FAULT soup_xmlrpc_fault_quark()
+GQuark soup_xmlrpc_fault_quark (void);
+
+typedef enum {
+       SOUP_XMLRPC_FAULT_PARSE_ERROR_NOT_WELL_FORMED = -32700,
+       SOUP_XMLRPC_FAULT_PARSE_ERROR_UNSUPPORTED_ENCODING = -32701,
+       SOUP_XMLRPC_FAULT_PARSE_ERROR_INVALID_CHARACTER_FOR_ENCODING = -32702,
+       SOUP_XMLRPC_FAULT_SERVER_ERROR_INVALID_XML_RPC = -32600,
+       SOUP_XMLRPC_FAULT_SERVER_ERROR_REQUESTED_METHOD_NOT_FOUND = -32601,
+       SOUP_XMLRPC_FAULT_SERVER_ERROR_INVALID_METHOD_PARAMETERS = -32602,
+       SOUP_XMLRPC_FAULT_SERVER_ERROR_INTERNAL_XML_RPC_ERROR = -32603,
+       SOUP_XMLRPC_FAULT_APPLICATION_ERROR = -32500,
+       SOUP_XMLRPC_FAULT_SYSTEM_ERROR = -32400,
+       SOUP_XMLRPC_FAULT_TRANSPORT_ERROR = -32300
+} SoupXMLRPCFault;
+
+G_END_DECLS
+
+#endif /* SOUP_XMLRPC_GVALUE_H */
diff --git a/libsoup/soup-xmlrpc.c b/libsoup/soup-xmlrpc.c
index c09c8c7..e7a7c19 100644
--- a/libsoup/soup-xmlrpc.c
+++ b/libsoup/soup-xmlrpc.c
@@ -2,7 +2,13 @@
 /*
  * soup-xmlrpc.c: XML-RPC parser/generator
  *
- * Copyright (C) 2007 Red Hat, Inc.
+ * Copyright 2007 Red Hat, Inc.
+ * Copyright 2007 OpenedHand Ltd.
+ * Copyright 2015 Collabora ltd.
+ *
+ * Author:
+ *   Eduardo Lima Mitev  <elima igalia com>
+ *   Xavier Claessens <xavier claessens collabora com>
  */
 
 #ifdef HAVE_CONFIG_H
@@ -10,157 +16,280 @@
 #endif
 
 #include <string.h>
-
+#include <errno.h>
 #include <libxml/tree.h>
-
 #include "soup-xmlrpc.h"
 #include "soup.h"
 
-/**
- * SECTION:soup-xmlrpc
- * @short_description: XML-RPC support
- *
- **/
+static gboolean insert_value (xmlNode  *parent, GVariant *value, GError **error);
 
-/* This whole file is pretty much deprecated and replaced by
- * soup-xmlrpc-variant.c */
-G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+static gboolean
+insert_array (xmlNode *parent, GVariant *value, GError **error)
+{
+       xmlNode *node;
+       GVariantIter iter;
+       GVariant *child;
+
+       node = xmlNewChild (parent, NULL,
+                           (const xmlChar *)"array", NULL);
+       node = xmlNewChild (node, NULL,
+                           (const xmlChar *)"data", NULL);
+
+       g_variant_iter_init (&iter, value);
+       while ((child = g_variant_iter_next_value (&iter))) {
+               if (!insert_value (node, child, error)) {
+                       g_variant_unref (child);
+                       return FALSE;
+               }
+               g_variant_unref (child);
+       }
 
-static xmlNode *find_real_node (xmlNode *node);
+       return TRUE;
+}
 
-static gboolean insert_value (xmlNode *parent, GValue *value);
+static gboolean
+insert_struct_member (xmlNode *parent, GVariant *value, GError **error)
+{
+       xmlNode *member;
+       GVariant *mname;
+       GVariant *mvalue;
+       gboolean ret = FALSE;
+
+       mname = g_variant_get_child_value (value, 0);
+       mvalue = g_variant_get_child_value (value, 1);
+
+       if (g_variant_classify (mname) != G_VARIANT_CLASS_STRING) {
+               g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                            "Only string keys are supported in dictionaries, got %s",
+                            g_variant_get_type_string (mname));
+               goto fail;
+       }
+
+       member = xmlNewChild (parent, NULL,
+                             (const xmlChar *)"member", NULL);
+
+       xmlNewTextChild (member, NULL,
+                        (const xmlChar *)"name",
+                        (const xmlChar *)g_variant_get_string (mname, NULL));
+
+       ret = insert_value (member, mvalue, error);
+
+fail:
+       g_variant_unref (mname);
+       g_variant_unref (mvalue);
+
+       return ret;
+}
 
 static gboolean
-insert_value (xmlNode *parent, GValue *value)
+insert_struct (xmlNode *parent, GVariant *value, GError **error)
+{
+       xmlNode *struct_node;
+       GVariantIter iter;
+       GVariant *child;
+
+       struct_node = xmlNewChild (parent, NULL,
+                                  (const xmlChar *)"struct", NULL);
+
+       g_variant_iter_init (&iter, value);
+       while ((child = g_variant_iter_next_value (&iter))) {
+               if (!insert_struct_member (struct_node, child, error)) {
+                       g_variant_unref (child);
+                       return FALSE;
+               }
+               g_variant_unref (child);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+insert_value (xmlNode *parent, GVariant *value, GError **error)
 {
-       GType type = G_VALUE_TYPE (value);
        xmlNode *xvalue;
+       const char *type_str = NULL;
        char buf[128];
 
        xvalue = xmlNewChild (parent, NULL, (const xmlChar *)"value", NULL);
 
-       if (type == G_TYPE_INT) {
-               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) {
-               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) {
+       switch (g_variant_classify (value)) {
+       case G_VARIANT_CLASS_BOOLEAN:
+               snprintf (buf, sizeof (buf), "%d", g_variant_get_boolean (value));
+               type_str = "boolean";
+               break;
+       case G_VARIANT_CLASS_BYTE:
+               snprintf (buf, sizeof (buf), "%d", g_variant_get_byte (value));
+               type_str = "int";
+               break;
+       case G_VARIANT_CLASS_INT16:
+               snprintf (buf, sizeof (buf), "%d", g_variant_get_int16 (value));
+               type_str = "int";
+               break;
+       case G_VARIANT_CLASS_UINT16:
+               snprintf (buf, sizeof (buf), "%d", g_variant_get_uint16 (value));
+               type_str = "int";
+               break;
+       case G_VARIANT_CLASS_INT32:
+               snprintf (buf, sizeof (buf), "%d", g_variant_get_int32 (value));
+               type_str = "int";
+               break;
+       case G_VARIANT_CLASS_UINT32:
+               snprintf (buf, sizeof (buf), "%u", g_variant_get_uint32 (value));
+               type_str = "i8";
+               break;
+       case G_VARIANT_CLASS_INT64:
+               snprintf (buf, sizeof (buf), "%"G_GINT64_FORMAT, g_variant_get_int64 (value));
+               type_str = "i8";
+               break;
+       case G_VARIANT_CLASS_DOUBLE:
+               g_ascii_dtostr (buf, sizeof (buf), g_variant_get_double (value));
+               type_str = "double";
+               break;
+       case G_VARIANT_CLASS_STRING:
+       case G_VARIANT_CLASS_OBJECT_PATH:
+       case G_VARIANT_CLASS_SIGNATURE:
                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;
+                                (const xmlChar *)"string",
+                                (const xmlChar *)g_variant_get_string (value, NULL));
+               break;
+       case G_VARIANT_CLASS_VARIANT: {
+               GVariant *child;
+
+               xmlUnlinkNode (xvalue);
+               xmlFreeNode (xvalue);
+
+               child = g_variant_get_variant (value);
+               if (!insert_value (parent, child, error)) {
+                       g_variant_unref (child);
+                       return FALSE;
+               }
+               g_variant_unref (child);
+               break;
+       }
+       case G_VARIANT_CLASS_ARRAY: {
+               if (g_variant_is_of_type (value, G_VARIANT_TYPE_BYTESTRING)) {
+                       char *encoded;
+
+                       encoded = g_base64_encode (g_variant_get_data (value),
+                                                  g_variant_get_size (value));
+                       xmlNewChild (xvalue, NULL,
+                                    (const xmlChar *)"base64",
+                                    (const xmlChar *)encoded);
+                       g_free (encoded);
+               } else if (g_variant_is_of_type (value, G_VARIANT_TYPE_DICTIONARY)) {
+                       if (!insert_struct (xvalue, value, error))
+                               return FALSE;
+               } else {
+                       if (!insert_array (xvalue, value, error))
+                               return FALSE;
+               }
+
+               break;
+       }
+       case G_VARIANT_CLASS_TUPLE: {
+               /* Special case for custom types */
+               if (g_variant_is_of_type (value, G_VARIANT_TYPE ("(oss)"))) {
+                       const char *path;
+                       const char *type;
+                       const char *v;
+
+                       g_variant_get (value, "(&o&s&s)", &path, &type, &v);
+                       if (g_str_equal (path, "/org/gnome/libsoup/ExtensionType")) {
+                               xmlNewTextChild (xvalue, NULL,
+                                                (const xmlChar *)type,
+                                                (const xmlChar *)v);
                                break;
                        }
                }
 
-               if (!struct_node)
+               if (!insert_array (xvalue, value, error))
                        return FALSE;
-       } else if (type == G_TYPE_VALUE_ARRAY) {
-               GValueArray *va = g_value_get_boxed (value);
+               break;
+       }
+       case G_VARIANT_CLASS_DICT_ENTRY: {
                xmlNode *node;
-               int 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;
+                                   (const xmlChar *)"struct", NULL);
+               if (!insert_struct_member (node, value, error))
+                       return FALSE;
+               break;
+       }
+       case G_VARIANT_CLASS_HANDLE:
+       case G_VARIANT_CLASS_MAYBE:
+       case G_VARIANT_CLASS_UINT64:
+       default:
+               g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                            "Unsupported type: %s", g_variant_get_type_string (value));
+               goto fail;
+       }
+
+       if (type_str != NULL) {
+               xmlNewTextChild (xvalue, NULL,
+                                (const xmlChar *)type_str,
+                                (const xmlChar *)buf);
+       }
 
        return TRUE;
+
+fail:
+       return FALSE;
 }
 
 /**
- * soup_xmlrpc_build_method_call:
+ * soup_xmlrpc_build_request:
  * @method_name: the name of the XML-RPC method
- * @params: (array length=n_params): arguments to @method
- * @n_params: length of @params
+ * @params: a #GVariant tuple
+ * @error: a #GError, or %NULL
  *
  * This creates an XML-RPC methodCall and returns it as a string.
- * This is the low-level method that soup_xmlrpc_request_new() is
+ * This is the low-level method that soup_xmlrpc_message_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.)
+ * @params is a #GVariant tuple representing the method parameters.
  *
- * The correspondence between glib types and XML-RPC types is:
+ * Serialization details:
+ *  - "a{s*}" and "{s*}" are serialized as &lt;struct&gt;
+ *  - "ay" are serialized as &lt;base64&gt;
+ *  - Other arrays and tuples are serialized as &lt;array&gt;
+ *  - booleans are serialized as &lt;boolean&gt;
+ *  - byte, int16, uint16 and int32 are serialized as &lt;int&gt;
+ *  - uint32 and int64 are serialized as nonstandard &lt;i8&gt;
+ *  - doubles are serialized as &lt;double&gt;
+ *  - Strings, object-paths and signatures are serialized as &lt;string&gt;
+ *  - Variants (i.e. "v" type) are unwrapped and their child is serialized.
+ *  - #GVariant created by soup_xmlrpc_new_datetime() are serialized as
+ *    &lt;dateTime.iso8601&gt;
+ *  - Other types are not supported and will return %NULL and set @error.
+ *    This notably includes: uint64, maybes and dictionaries with non-string
+ *    keys.
  *
- *   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)
+ * If @params is floating, it is consumed.
  *
- * For structs, use a #GHashTable that maps strings to #GValue;
- * soup_value_hash_new() and related methods can help with this.
- *
- * Deprecated: Use soup_xmlrpc_build_request() instead.
- * Return value: (nullable): the text of the methodCall, or %NULL on
- * error
+ * Return value: the text of the methodCall, or %NULL on error.
+ * Since: 2.52
  **/
 char *
-soup_xmlrpc_build_method_call (const char *method_name,
-                              GValue *params, int n_params)
+soup_xmlrpc_build_request (const char *method_name,
+                          GVariant    *params,
+                          GError     **error)
 {
        xmlDoc *doc;
        xmlNode *node, *param;
        xmlChar *xmlbody;
-       int i, len;
-       char *body;
+       GVariantIter iter;
+       GVariant *child;
+       int len;
+       char *body = NULL;
+
+       g_variant_ref_sink (params);
+
+       if (!g_variant_is_of_type (params, G_VARIANT_TYPE_TUPLE)) {
+               g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                            "params must be a tuple but is %s",
+                            g_variant_get_type_string (params));
+               goto fail;
+       }
 
        doc = xmlNewDoc ((const xmlChar *)"1.0");
        doc->standalone = FALSE;
@@ -172,92 +301,85 @@ soup_xmlrpc_build_method_call (const char *method_name,
                     (const xmlChar *)method_name);
 
        node = xmlNewChild (node, NULL, (const xmlChar *)"params", NULL);
-       for (i = 0; i < n_params; i++) {
+       g_variant_iter_init (&iter, params);
+       while ((child = g_variant_iter_next_value (&iter))) {
                param  = xmlNewChild (node, NULL,
                                      (const xmlChar *)"param", NULL);
-               if (!insert_value (param, &params[i])) {
+               if (!insert_value (param, child, error)) {
                        xmlFreeDoc (doc);
+                       g_variant_unref (child);
+                       g_variant_unref (params);
                        return NULL;
                }
+               g_variant_unref (child);
        }
 
        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;
+       xmlFreeDoc (doc);
+       g_variant_unref (params);
 
-       msg = soup_message_new ("POST", uri);
-       soup_message_set_request (msg, "text/xml", SOUP_MEMORY_TAKE,
-                                 body, strlen (body));
-       return msg;
+fail:
+       return body;
 }
 
 /**
- * soup_xmlrpc_request_new:
+ * soup_xmlrpc_message_new:
  * @uri: URI of the XML-RPC service
  * @method_name: the name of the XML-RPC method to invoke at @uri
- * @...: parameters for @method
+ * @params: a #GVariant tuple
+ * @error: a #GError, or %NULL
  *
  * 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.
+ * See soup_xmlrpc_build_request() for serialization details.
  *
- * Deprecated: Use soup_xmlrpc_message_new() instead.
- * Return value: (transfer full): a #SoupMessage encoding the
- * indicated XML-RPC request.
+ * If @params is floating, it is consumed.
+ *
+ * Returns: (transfer full): a #SoupMessage encoding the
+ *  indicated XML-RPC request, or %NULL on error.
+ * Since: 2.52
  **/
 SoupMessage *
-soup_xmlrpc_request_new (const char *uri, const char *method_name, ...)
+soup_xmlrpc_message_new (const char *uri,
+                        const char *method_name,
+                        GVariant    *params,
+                        GError     **error)
 {
        SoupMessage *msg;
-       va_list args;
+       char *body;
 
-       va_start (args, method_name);
-       msg = soup_xmlrpc_request_newv (uri, method_name, args);
-       va_end (args);
+       body = soup_xmlrpc_build_request (method_name, params, error);
+       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_build_method_response:
+ * soup_xmlrpc_build_response:
  * @value: the return value
+ * @error: a #GError, or %NULL
  *
  * This creates a (successful) XML-RPC methodResponse and returns it
- * as a string. To create a fault response, use
- * soup_xmlrpc_build_fault().
+ * as a string. To create a fault response, use soup_xmlrpc_build_fault(). This
+ * is the low-level method that soup_xmlrpc_message_set_response() is built on.
+ *
+ * See soup_xmlrpc_build_request() for serialization details.
  *
- * The glib type to XML-RPC type mapping is as with
- * soup_xmlrpc_build_method_call(), qv.
+ * If @value is floating, it is consumed.
  *
- * Deprecated: Use soup_xmlrpc_build_response() instead.
- * Return value: (nullable): the text of the methodResponse, or %NULL
- * on error
+ * Returns: the text of the methodResponse, or %NULL on error.
+ * Since: 2.52
  **/
 char *
-soup_xmlrpc_build_method_response (GValue *value)
+soup_xmlrpc_build_response (GVariant *value, GError **error)
 {
        xmlDoc *doc;
        xmlNode *node;
@@ -265,6 +387,8 @@ soup_xmlrpc_build_method_response (GValue *value)
        char *body;
        int len;
 
+       g_variant_ref_sink (value);
+
        doc = xmlNewDoc ((const xmlChar *)"1.0");
        doc->standalone = FALSE;
        doc->encoding = xmlCharStrdup ("UTF-8");
@@ -275,613 +399,931 @@ soup_xmlrpc_build_method_response (GValue *value)
 
        node = xmlNewChild (node, NULL, (const xmlChar *)"params", NULL);
        node = xmlNewChild (node, NULL, (const xmlChar *)"param", NULL);
-       if (!insert_value (node, value)) {
+       if (!insert_value (node, value, error)) {
                xmlFreeDoc (doc);
+               g_variant_unref (value);
                return NULL;
        }
 
        xmlDocDumpMemory (doc, &xmlbody, &len);
        body = g_strndup ((char *)xmlbody, len);
        xmlFree (xmlbody);
+
        xmlFreeDoc (doc);
+       g_variant_unref (value);
+
        return body;
 }
 
-static char *
-soup_xmlrpc_build_faultv (int         fault_code,
-                          const char *fault_format,
-                          va_list     args) G_GNUC_PRINTF (2, 0);
+/**
+ * soup_xmlrpc_message_set_response:
+ * @msg: an XML-RPC request
+ * @value: a #GVariant
+ * @error: a #GError, or %NULL
+ *
+ * Sets the status code and response body of @msg to indicate a
+ * successful XML-RPC call, with a return value given by @value. To set a
+ * fault response, use soup_xmlrpc_set_fault().
+ *
+ * See soup_xmlrpc_build_request() for serialization details.
+ *
+ * If @value is floating, it is consumed.
+ *
+ * Returns: %TRUE on success, %FALSE otherwise.
+ * Since: 2.52
+ **/
+gboolean
+soup_xmlrpc_message_set_response (SoupMessage *msg, GVariant *value, GError **error)
+{
+       char *body;
+
+       body = soup_xmlrpc_build_response (value, error);
+       if (!body)
+               return FALSE;
+
+       soup_message_set_status (msg, SOUP_STATUS_OK);
+       soup_message_set_response (msg, "text/xml", SOUP_MEMORY_TAKE,
+                                  body, strlen (body));
+       return TRUE;
+}
+
+static GVariant *parse_value (xmlNode *node, const char **signature, GError **error);
+
+static xmlNode *
+find_real_node (xmlNode *node)
+{
+       while (node && (node->type == XML_COMMENT_NODE ||
+                       xmlIsBlankNode (node)))
+               node = node->next;
+       return node;
+}
 
 static char *
-soup_xmlrpc_build_faultv (int fault_code, const char *fault_format, va_list args)
+signature_get_next_complete_type (const char **signature)
 {
-       xmlDoc *doc;
-       xmlNode *node, *member;
-       GValue value;
-       xmlChar *xmlbody;
-       char *fault_string, *body;
-       int len;
+       GVariantClass class;
+       const char *initial_signature;
+       char *result;
 
-       fault_string = g_strdup_vprintf (fault_format, args);
+       /* here it is assumed that 'signature' is a valid type string */
 
-       doc = xmlNewDoc ((const xmlChar *)"1.0");
-       doc->standalone = FALSE;
-       doc->encoding = xmlCharStrdup ("UTF-8");
+       initial_signature = *signature;
+       class = (*signature)[0];
 
-       node = xmlNewDocNode (doc, NULL,
-                             (const xmlChar *)"methodResponse", NULL);
-       xmlDocSetRootElement (doc, node);
-       node = xmlNewChild (node, NULL, (const xmlChar *)"fault", NULL);
-       node = xmlNewChild (node, NULL, (const xmlChar *)"value", NULL);
-       node = xmlNewChild (node, NULL, (const xmlChar *)"struct", NULL);
-
-       memset (&value, 0, sizeof (value));
-
-       member = xmlNewChild (node, NULL, (const xmlChar *)"member", NULL);
-       xmlNewChild (member, NULL,
-                    (const xmlChar *)"name", (const xmlChar *)"faultCode");
-       g_value_init (&value, G_TYPE_INT);
-       g_value_set_int (&value, fault_code);
-       insert_value (member, &value);
-       g_value_unset (&value);
-
-       member = xmlNewChild (node, NULL, (const xmlChar *)"member", NULL);
-       xmlNewChild (member, NULL,
-                    (const xmlChar *)"name", (const xmlChar *)"faultString");
-       g_value_init (&value, G_TYPE_STRING);
-       g_value_take_string (&value, fault_string);
-       insert_value (member, &value);
-       g_value_unset (&value);
+       if (class == G_VARIANT_CLASS_TUPLE || class == G_VARIANT_CLASS_DICT_ENTRY) {
+               char stack[256] = {0};
+               guint stack_len = 0;
 
-       xmlDocDumpMemory (doc, &xmlbody, &len);
-       body = g_strndup ((char *)xmlbody, len);
-       xmlFree (xmlbody);
-       xmlFreeDoc (doc);
+               do {
+                       if ((*signature)[0] == G_VARIANT_CLASS_TUPLE) {
+                               stack[stack_len] = ')';
+                               stack_len++;
+                       }
+                       else if ( (*signature)[0] == G_VARIANT_CLASS_DICT_ENTRY) {
+                               stack[stack_len] = '}';
+                               stack_len++;
+                       }
 
-       return body;
+                       (*signature)++;
+
+                       if ( (*signature)[0] == stack[stack_len - 1])
+                               stack_len--;
+               } while (stack_len > 0);
+
+               (*signature)++;
+       } else if (class == G_VARIANT_CLASS_ARRAY || class == G_VARIANT_CLASS_MAYBE) {
+               char *tmp_sig;
+
+               (*signature)++;
+               tmp_sig = signature_get_next_complete_type (signature);
+               g_free (tmp_sig);
+       } else {
+               (*signature)++;
+       }
+
+       result = g_strndup (initial_signature, (*signature) - initial_signature);
+
+       return result;
 }
 
-/**
- * soup_xmlrpc_build_fault:
- * @fault_code: the fault code
- * @fault_format: a printf()-style format string
- * @...: the parameters to @fault_format
- *
- * This creates an XML-RPC fault response and returns it as a string.
- * (To create a successful response, use
- * soup_xmlrpc_build_method_response().)
- *
- * Return value: the text of the fault
- **/
-char *
-soup_xmlrpc_build_fault (int fault_code, const char *fault_format, ...)
+static GVariant *
+parse_array (xmlNode *node, const char **signature, GError **error)
 {
-       va_list args;
-       char *body;
+       GVariant *variant = NULL;
+       char *child_signature = NULL;
+       char *array_signature = NULL;
+       const char *tmp_signature;
+       gboolean is_tuple = FALSE;
+       xmlNode *member;
+       GVariantBuilder builder;
+       gboolean is_params = FALSE;
+
+       if (signature && *signature[0] == G_VARIANT_CLASS_VARIANT)
+               signature = NULL;
+
+       if (g_str_equal (node->name, "array")) {
+               node = find_real_node (node->children);
+               if (!g_str_equal (node->name, "data")) {
+                       g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                                    "<data> expected but got '%s'", node->name);
+                       goto fail;
+               }
+       } else if (g_str_equal (node->name, "params")) {
+               is_params = TRUE;
+       } else {
+               g_assert_not_reached ();
+       }
 
-       va_start (args, fault_format);
-       body = soup_xmlrpc_build_faultv (fault_code, fault_format, args);
-       va_end (args);
-       return body;
+       if (signature != NULL) {
+               if ((*signature)[0] == G_VARIANT_CLASS_TUPLE) {
+                       tmp_signature = *signature;
+                       array_signature = signature_get_next_complete_type (&tmp_signature);
+                       is_tuple = TRUE;
+               }
+               (*signature)++;
+               child_signature = signature_get_next_complete_type (signature);
+       } else {
+               child_signature = g_strdup ("v");
+       }
+
+       if (!array_signature)
+               array_signature = g_strdup_printf ("a%s", child_signature);
+       g_variant_builder_init (&builder, G_VARIANT_TYPE (array_signature));
+
+       for (member = find_real_node (node->children);
+            member;
+            member = find_real_node (member->next)) {
+               GVariant *child;
+               xmlNode *xval = member;
+
+               if (is_params) {
+                       if (!g_str_equal (member->name, "param")) {
+                               g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                                            "<param> expected but got '%s'", member->name);
+                               goto fail;
+                       }
+                       xval = find_real_node (member->children);
+               }
+
+               if (strcmp ((const char *)xval->name, "value") != 0) {
+                       g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                                    "<value> expected but got '%s'", xval->name);
+                       goto fail;
+               }
+
+               if (is_tuple && child_signature[0] == ')') {
+                       g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                                    "Too many values for tuple");
+                       goto fail;
+               }
+
+               tmp_signature = child_signature;
+               child = parse_value (xval, &tmp_signature, error);
+               if (child == NULL)
+                       goto fail;
+
+               if (is_tuple) {
+                       g_free (child_signature),
+                       child_signature = signature_get_next_complete_type (signature);
+               }
+
+               g_variant_builder_add_value (&builder, child);
+       }
+
+       if (is_tuple && child_signature[0] != ')') {
+               g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                            "Too few values for tuple");
+               goto fail;
+       }
+
+       variant = g_variant_builder_end (&builder);
+
+fail:
+       g_variant_builder_clear (&builder);
+       g_free (child_signature);
+       g_free (array_signature);
+
+       /* compensate the (*signature)++ call at the end of 'recurse()' */
+       if (signature)
+               (*signature)--;
+
+       return variant;
 }
 
-/**
- * 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, ...)
+static void
+parse_dict_entry_signature (const char **signature,
+                           char       **entry_signature,
+                           char       **key_signature,
+                           char       **value_signature)
 {
-       va_list args;
-       GValue value;
-       char *body;
+       const char *tmp_sig;
 
-       va_start (args, type);
-       SOUP_VALUE_SETV (&value, type, args);
-       va_end (args);
+       if (signature)
+               *entry_signature = signature_get_next_complete_type (signature);
+       else
+               *entry_signature = g_strdup ("{sv}");
 
-       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));
+       tmp_sig = (*entry_signature) + 1;
+       *key_signature = signature_get_next_complete_type (&tmp_sig);
+       *value_signature = signature_get_next_complete_type (&tmp_sig);
 }
 
-/**
- * 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, ...)
+static GVariant *
+parse_dictionary (xmlNode *node, const char **signature, GError **error)
 {
-       va_list args;
-       char *body;
+       GVariant *variant = NULL;
+       char *dict_signature;
+       char *entry_signature;
+       char *key_signature;
+       char *value_signature;
+       GVariantBuilder builder;
+       xmlNode *member;
+
+       if (signature && *signature[0] == G_VARIANT_CLASS_VARIANT)
+               signature = NULL;
+
+       if (signature)
+               (*signature)++;
+
+       parse_dict_entry_signature (signature,
+                                   &entry_signature,
+                                   &key_signature,
+                                   &value_signature);
+
+       dict_signature = g_strdup_printf ("a%s", entry_signature);
+       g_variant_builder_init (&builder, G_VARIANT_TYPE (dict_signature));
+
+       if (!g_str_equal (key_signature, "s")) {
+               g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                            "Dictionary key must be string but got '%s'", key_signature);
+               goto fail;
+       }
 
-       va_start (args, fault_format);
-       body = soup_xmlrpc_build_faultv (fault_code, fault_format, args);
-       va_end (args);
+       for (member = find_real_node (node->children);
+            member;
+            member = find_real_node (member->next)) {
+               xmlNode *child, *mname, *mxval;
+               const char *tmp_signature;
+               GVariant *value;
+               xmlChar *content;
+
+               if (strcmp ((const char *)member->name, "member") != 0) {
+                       g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                                    "<member> expected but got '%s'", member->name);
+                       goto fail;
+               }
 
-       soup_message_set_status (msg, SOUP_STATUS_OK);
-       soup_message_set_response (msg, "text/xml", SOUP_MEMORY_TAKE,
-                                  body, strlen (body));
+               mname = mxval = NULL;
+
+               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 {
+                               g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                                            "<name> or <value> expected but got '%s'", child->name);
+                               goto fail;
+                       }
+               }
+
+               if (!mname || !mxval) {
+                       g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                                    "Missing name or value in <member>");
+                       goto fail;
+               }
+
+               tmp_signature = value_signature;
+               value = parse_value (mxval, &tmp_signature, error);
+               if (!value)
+                       goto fail;
+
+               content = xmlNodeGetContent (mname);
+               g_variant_builder_open (&builder, G_VARIANT_TYPE (entry_signature));
+               g_variant_builder_add (&builder, "s", content);
+               g_variant_builder_add_value (&builder, value);
+               g_variant_builder_close (&builder);
+               xmlFree (content);
+       }
+
+       variant = g_variant_builder_end (&builder);
+
+fail:
+       g_variant_builder_clear (&builder);
+       g_free (value_signature);
+       g_free (key_signature);
+       g_free (entry_signature);
+       g_free (dict_signature);
+
+       /* compensate the (*signature)++ call at the end of 'recurse()' */
+       if (signature != NULL)
+               (*signature)--;
+
+       return variant;
 }
 
-/**
- * soup_xmlrpc_message_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.
- **/
-void
-soup_xmlrpc_message_set_fault (SoupMessage *msg, int fault_code,
-                              const char *fault_format, ...)
+static GVariant *
+parse_number (xmlNode *typenode, GVariantClass class, GError **error)
 {
-       va_list args;
-       char *body;
+       xmlChar *content;
+       const char *str;
+       char *endptr;
+       gint64 num = 0;
+       guint64 unum = 0;
+       GVariant *variant = NULL;
 
-       va_start (args, fault_format);
-       body = soup_xmlrpc_build_faultv (fault_code, fault_format, args);
-       va_end (args);
+       content = xmlNodeGetContent (typenode);
+       str = (const char *) content;
 
-       soup_message_set_status (msg, SOUP_STATUS_OK);
-       soup_message_set_response (msg, "text/xml", SOUP_MEMORY_TAKE,
-                                  body, strlen (body));
+       errno = 0;
+
+       if (class == G_VARIANT_CLASS_UINT64)
+               unum = g_ascii_strtoull (str, &endptr, 10);
+       else
+               num = g_ascii_strtoll (str, &endptr, 10);
+
+       if (errno || endptr == str) {
+               g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                            "Couldn't parse number '%s'", str);
+               goto fail;
+       }
+
+#define RANGE(v, min, max) \
+G_STMT_START{ \
+       if (v < min || v > max) { \
+               g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS, \
+                            "Number out of range '%s'", str); \
+               goto fail; \
+       } \
+} G_STMT_END
+
+       switch (class) {
+       case G_VARIANT_CLASS_BOOLEAN:
+               RANGE (num, 0, 1);
+               variant = g_variant_new_boolean (num);
+               break;
+       case G_VARIANT_CLASS_BYTE:
+               RANGE (num, 0, G_MAXUINT8);
+               variant = g_variant_new_byte (num);
+               break;
+       case G_VARIANT_CLASS_INT16:
+               RANGE (num, G_MININT16, G_MAXINT16);
+               variant = g_variant_new_int16 (num);
+               break;
+       case G_VARIANT_CLASS_UINT16:
+               RANGE (num, 0, G_MAXUINT16);
+               variant = g_variant_new_uint16 (num);
+               break;
+       case G_VARIANT_CLASS_INT32:
+               RANGE (num, G_MININT32, G_MAXINT32);
+               variant = g_variant_new_int32 (num);
+               break;
+       case G_VARIANT_CLASS_UINT32:
+               RANGE (num, 0, G_MAXUINT32);
+               variant = g_variant_new_uint32 (num);
+               break;
+       case G_VARIANT_CLASS_INT64:
+               RANGE (num, G_MININT64, G_MAXINT64);
+               variant = g_variant_new_int64 (num);
+               break;
+       case G_VARIANT_CLASS_UINT64:
+               RANGE (unum, 0, G_MAXUINT64);
+               variant = g_variant_new_uint64 (unum);
+               break;
+       default:
+               g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                            "<%s> node does not match signature",
+                            (const char *)typenode->name);
+               goto fail;
+       }
+
+fail:
+       xmlFree (content);
+
+       return variant;
 }
 
-static gboolean
-parse_value (xmlNode *xmlvalue, GValue *value)
+static GVariant *
+parse_double (xmlNode *typenode, GError **error)
 {
-       xmlNode *typenode;
-       const char *typename;
+       GVariant *variant = NULL;
        xmlChar *content;
+       const char *str;
+       char *endptr;
+       gdouble d;
+
+       content = xmlNodeGetContent (typenode);
+       str = (const char *) content;
+
+       errno = 0;
+       d = g_ascii_strtod (str, &endptr);
+       if (errno || endptr == str) {
+               g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                            "Couldn't parse double '%s'", str);
+               goto fail;
+       }
+
+       variant = g_variant_new_double (d);
 
-       memset (value, 0, sizeof (GValue));
+fail:
+       xmlFree (content);
+
+       return variant;
+}
 
-       typenode = find_real_node (xmlvalue->children);
-       if (!typenode) {
-               /* If no type node, it's a string */
+static GVariant *
+parse_base64 (xmlNode *typenode, GError **error)
+{
+       GVariant *variant;
+       xmlChar *content;
+       guchar *decoded;
+       gsize len;
+
+       content = xmlNodeGetContent (typenode);
+       decoded = g_base64_decode ((char *)content, &len);
+       variant = g_variant_new_from_data (G_VARIANT_TYPE ("ay"),
+                                          decoded, len,
+                                          TRUE,
+                                          g_free, decoded);
+       xmlFree (content);
+
+       return variant;
+}
+
+static GVariant *
+parse_value (xmlNode *node, const char **signature, GError **error)
+{
+       xmlNode *typenode;
+       const char *typename;
+       xmlChar *content = NULL;
+       GVariant *variant = NULL;
+       GVariantClass class = G_VARIANT_CLASS_VARIANT;
+
+       if (signature)
+               class = *signature[0];
+
+       if (g_str_equal ((const char *)node->name, "value")) {
+               typenode = find_real_node (node->children);
+               if (!typenode) {
+                       /* If no typenode, assume value's content is string */
+                       typename = "string";
+                       typenode = node;
+               } else {
+                       typename = (const char *)typenode->name;
+               }
+       } else if (g_str_equal ((const char *)node->name, "params")) {
+               typenode = node;
+               typename = "params";
+       } else {
+               g_assert_not_reached ();
+       }
+
+       if (g_str_equal (typename, "boolean")) {
+               if (class != G_VARIANT_CLASS_VARIANT && class != G_VARIANT_CLASS_BOOLEAN) {
+                       g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                                    "<boolean> node does not match signature");
+                       goto fail;
+               }
+               variant = parse_number (typenode, G_VARIANT_CLASS_BOOLEAN, error);
+       } else if (g_str_equal (typename, "int") || g_str_equal (typename, "i4")) {
+               if (class == G_VARIANT_CLASS_VARIANT)
+                       variant = parse_number (typenode, G_VARIANT_CLASS_INT32, error);
+               else
+                       variant = parse_number (typenode, class, error);
+       } else if (g_str_equal (typename, "i8")) {
+               if (class == G_VARIANT_CLASS_VARIANT)
+                       variant = parse_number (typenode, G_VARIANT_CLASS_INT64, error);
+               else
+                       variant = parse_number (typenode, class, error);
+       } else  if (g_str_equal (typename, "double")) {
+               if (class == G_VARIANT_CLASS_VARIANT || class == G_VARIANT_CLASS_DOUBLE) {
+                       g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                                    "<double> node does not match signature");
+                       goto fail;
+               }
+               variant = parse_double (typenode, error);
+       } else  if (g_str_equal (typename, "string")) {
                content = xmlNodeGetContent (typenode);
-               g_value_init (value, G_TYPE_STRING);
-               g_value_set_string (value, (char *)content);
-               xmlFree (content);
-               return TRUE;
+               if (class == G_VARIANT_CLASS_VARIANT || class == G_VARIANT_CLASS_STRING)
+                       variant = g_variant_new_string ((const char *)content);
+               else if (class == G_VARIANT_CLASS_OBJECT_PATH &&
+                        g_variant_is_object_path ((const char *)content))
+                       variant = g_variant_new_object_path ((const char *)content);
+               else if (class == G_VARIANT_CLASS_SIGNATURE &&
+                        g_variant_is_signature ((const char *)content))
+                       variant = g_variant_new_signature ((const char *)content);
+               else {
+                       g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                                    "<string> node does not match signature");
+                       goto fail;
+               }
+       } else if (g_str_equal (typename, "base64")) {
+               if (class != G_VARIANT_CLASS_VARIANT) {
+                       if (!g_str_has_prefix (*signature, "ay")) {
+                               g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                                            "<base64> node does not match signature");
+                               goto fail;
+                       }
+                       (*signature)++;
+               }
+               variant = parse_base64 (typenode, error);
+       } else if (g_str_equal (typename, "struct")) {
+               if (class != G_VARIANT_CLASS_VARIANT &&
+                   !g_str_has_prefix (*signature, "a{")) {
+                       g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                                    "<struct> node does not match signature");
+                       goto fail;
+               }
+               variant = parse_dictionary (typenode, signature, error);
+       } else if (g_str_equal (typename, "array") || g_str_equal (typename, "params")) {
+               if (class != G_VARIANT_CLASS_VARIANT &&
+                   class != G_VARIANT_CLASS_ARRAY &&
+                   class != G_VARIANT_CLASS_TUPLE) {
+                       g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                                    "<%s> node does not match signature", typename);
+                       goto fail;
+               }
+               variant = parse_array (typenode, signature, error);
+       } else if (g_str_equal (typename, "dateTime.iso8601")) {
+               GTimeVal t;
+
+               if (class != G_VARIANT_CLASS_VARIANT &&
+                   class != G_VARIANT_CLASS_INT64) {
+                       g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                                    "<dateTime.iso8601> node does not match signalture");
+                       goto fail;
+               }
+
+               content = xmlNodeGetContent (node);
+               if (!g_time_val_from_iso8601 ((char *)content, &t)) {
+                       g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                                    "Couldn't parse date: %s", content);
+                       goto fail;
+               }
+
+               variant = g_variant_new_int64 (t.tv_sec);
+       } else {
+               g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                            "Unknown node name %s", typename);
+               goto fail;
        }
 
-       typename = (const char *)typenode->name;
+       if (variant && signature) {
+               if (class == G_VARIANT_CLASS_VARIANT)
+                       variant = g_variant_new_variant (variant);
+               (*signature)++;
+       }
 
-       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));
+fail:
+       if (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;
-                       }
+       return variant;
+}
 
-                       if (!mname || !mxval || !parse_value (mxval, &mgval)) {
-                               g_hash_table_destroy (hash);
-                               return FALSE;
-                       }
+/**
+ * SoupXMLRPCParams:
+ *
+ * Opaque structure containing an XML-RPC value. Can be parsed using
+ * soup_xmlrpc_params_parse() and freed with soup_xmlrpc_params_free().
+ *
+ * Since: 2.52
+ */
+struct _SoupXMLRPCParams
+{
+  xmlNode *node;
+};
 
-                       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;
+/**
+ * soup_xmlrpc_params_free:
+ * @self: a SoupXMLRPCParams
+ *
+ * Free a #SoupXMLRPCParams returned by soup_xmlrpc_parse_request().
+ *
+ * Since: 2.52
+ */
+void
+soup_xmlrpc_params_free (SoupXMLRPCParams *self)
+{
+       g_return_if_fail (self != NULL);
 
-               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;
-                       }
+       if (self->node)
+               xmlFreeDoc (self->node->doc);
+       g_slice_free (SoupXMLRPCParams, self);
+}
 
-                       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;
+static SoupXMLRPCParams *
+soup_xmlrpc_params_new (xmlNode *node)
+{
+       SoupXMLRPCParams *self;
 
-       return TRUE;
+       self = g_slice_new (SoupXMLRPCParams);
+       self->node = node;
+
+       return self;
 }
 
 /**
- * soup_xmlrpc_parse_method_call:
+ * soup_xmlrpc_params_parse:
+ * @self: A #SoupXMLRPCParams
+ * @signature: (allow-none): A valid #GVariant type string, or %NULL
+ * @error: a #GError, or %NULL
+ *
+ * Parse method parameters returned by soup_xmlrpc_parse_request().
+ *
+ * See soup_xmlrpc_parse_request_full() for deserialization details.
+ *
+ * Returns: (transfer full): a new #GVariant, or %NULL
+ * Since: 2.52
+ */
+GVariant *
+soup_xmlrpc_params_parse (SoupXMLRPCParams *self,
+                         const char      *signature,
+                         GError          **error)
+{
+       GVariant *value = NULL;
+
+       g_return_val_if_fail (self, NULL);
+       g_return_val_if_fail (!signature || g_variant_type_string_is_valid (signature), NULL);
+
+       value = parse_value (self->node, signature ? &signature : NULL, error);
+
+       return value ? g_variant_ref_sink (value) : NULL;
+}
+
+/**
+ * soup_xmlrpc_parse_request:
  * @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
+ * @params: (out): on success, a new #SoupXMLRPCParams
+ * @error: a #GError, or %NULL
  *
- * 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.
+ * Parses @method_call and return the method name. Method parameters can be
+ * parsed later using soup_xmlrpc_params_parse(). If the signature of parameters
+ * is known in advance then soup_xmlrpc_parse_request_full() could be used
+ * instead.
  *
- * Deprecated: Use soup_xmlrpc_parse_request_full() instead.
- * Return value: success or failure.
+ * Returns: (transfer full): method's name, or %NULL on error.
+ * Since: 2.52
  **/
-gboolean
-soup_xmlrpc_parse_method_call (const char *method_call, int length,
-                              char **method_name, GValueArray **params)
+char *
+soup_xmlrpc_parse_request (const char *method_call,
+                          int length,
+                          SoupXMLRPCParams **params,
+                          GError **error)
 {
-       xmlDoc *doc;
-       xmlNode *node, *param, *xval;
+       xmlDoc *doc = NULL;
+       xmlNode *node;
        xmlChar *xmlMethodName = NULL;
-       gboolean success = FALSE;
-       GValue value;
+       char *method_name = NULL;
 
        doc = xmlParseMemory (method_call,
-                             length == -1 ? strlen (method_call) : length);
-       if (!doc)
-               return FALSE;
+                                 length == -1 ? strlen (method_call) : length);
+       if (!doc) {
+               g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                            "Could not parse XML document");
+               goto fail;
+       }
 
        node = xmlDocGetRootElement (doc);
-       if (!node || strcmp ((const char *)node->name, "methodCall") != 0)
+       if (!node || strcmp ((const char *)node->name, "methodCall") != 0) {
+               g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                            "<methodCall> node expected");
                goto fail;
+       }
 
        node = find_real_node (node->children);
-       if (!node || strcmp ((const char *)node->name, "methodName") != 0)
+       if (!node || strcmp ((const char *)node->name, "methodName") != 0) {
+               g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                            "<methodName> node expected");
                goto fail;
+       }
        xmlMethodName = xmlNodeGetContent (node);
 
-       node = find_real_node (node->next);
-       if (node) {
-               if (strcmp ((const char *)node->name, "params") != 0)
+       if (params) {
+               node = find_real_node (node->next);
+               if (!node || strcmp ((const char *)node->name, "params") != 0) {
+                       g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                                    "<params> node expected");
                        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 ();
+               *params = soup_xmlrpc_params_new (node);
+               doc = NULL;
+       }
 
-       success = TRUE;
-       *method_name = g_strdup ((char *)xmlMethodName);
+       method_name = g_strdup ((char *)xmlMethodName);
 
 fail:
-       xmlFreeDoc (doc);
+       if (doc)
+               xmlFreeDoc (doc);
        if (xmlMethodName)
                xmlFree (xmlMethodName);
-       return success;
+
+       return method_name;
 }
 
 /**
- * soup_xmlrpc_extract_method_call:
+ * soup_xmlrpc_parse_request_full:
  * @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.
+ * @signature: (allow-none): A valid #GVariant type string, or %NULL
+ * @parameters: (out): on success, a new #GVariant
+ * @error: a #GError, or %NULL
  *
- * 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.
+ * Parses @method_call and return the method name and set @parameters.
+ * soup_xmlrpc_parse_request() should be used instead if the method name must be
+ * known to determine @parameters' signature.
  *
- * See also soup_xmlrpc_parse_method_call(), which can be used if
- * you don't know the types of the parameters.
+ * Deserialization details:
+ *  - If @signalture is provided, &lt;int&gt and &lt;i4&gt can be deserialized
+ *    to byte, int16, uint16, int32, uint32, int64, uint64 or handle. Otherwise
+ *    it will be int32. If the value is out of range for the target type it will
+ *    return an @error.
+ *  - &lt;struct&gt; will be deserialized to "a{sv}". @signalture could define
+ *    another value type (e.g. "a{ss}").
+ *  - &lt;array&gt; will be deserialized to "av". @signalture could define
+ *    another element type (e.g. "as") or could be a tuple (e.g. "(ss)").
+ *  - &lt;base64&gt; will be deserialized to "ay".
+ *  - &lt;string&gt; will be deserialized to "s". @signalture could define
+ *    another type ("o" or "g").
+ *  - &lt;dateTime.iso8601&gt; will be deserialized to int64 unix timestamp.
+ *  - @signature must not have maybes, otherwise an @error is returned.
+ *  - Dictionaries must have string keys, otherwise an @error is returned.
  *
- * Deprecated: Use soup_xmlrpc_parse_request_full() instead.
- * Return value: success or failure.
+ * Returns: (transfer full): method's name, or %NULL on error.
+ * Since: 2.52
  **/
-gboolean
-soup_xmlrpc_extract_method_call (const char *method_call, int length,
-                                char **method_name, ...)
+char *
+soup_xmlrpc_parse_request_full (const char *method_call,
+                               int         length,
+                               const char *signature,
+                               GVariant   **parameters,
+                               GError     **error)
 {
-       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);
+       char *method_name;
+       SoupXMLRPCParams *params;
+
+       method_name = soup_xmlrpc_parse_request (method_call,
+                                                length,
+                                                parameters ? &params : NULL,
+                                                error);
+       if (!method_name)
+               return NULL;
 
-       g_value_array_free (params);
+       if (parameters) {
+               *parameters = soup_xmlrpc_params_parse (params, signature, error);
+               if (*parameters == NULL) {
+                       g_free (method_name);
+                       return NULL;
+               }
+       }
 
-       return success;
+       return method_name;
 }
 
 /**
- * soup_xmlrpc_parse_method_response:
+ * soup_xmlrpc_parse_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.)
- *
- * Deprecated: Use soup_xmlrpc_parse_response() instead.
- * Return value: %TRUE if a return value was parsed, %FALSE if the
- * response could not be parsed, or contained a fault.
+ * @signature: (allow-none): A valid #GVariant type string, or %NULL
+ * @error: a #GError, or %NULL
+ *
+ * Parses @method_response and returns the return value. If
+ * @method_response is a fault, %NULL is returned, and @error
+ * will be set to an error in the %SOUP_XMLRPC_FAULT domain, with the error
+ * code containing the fault code, and the error message containing
+ * the fault string. If @method_response cannot be parsed, %NULL is returned,
+ * and @error will be set to an error in the %SOUP_XMLRPC_ERROR domain.
+ *
+ * See soup_xmlrpc_parse_request_full() for deserialization details.
+ *
+ * Returns: (transfer full): a new #GVariant, or %NULL
+ * Since: 2.52
  **/
-gboolean
-soup_xmlrpc_parse_method_response (const char *method_response, int length,
-                                  GValue *value, GError **error)
+GVariant *
+soup_xmlrpc_parse_response (const char *method_response,
+                           int length,
+                           const char *signature,
+                           GError **error)
 {
-       xmlDoc *doc;
+       xmlDoc *doc = NULL;
        xmlNode *node;
-       gboolean success = FALSE;
+       GVariant *value = NULL;
+
+       g_return_val_if_fail (!signature || g_variant_type_string_is_valid (signature), NULL);
 
        doc = xmlParseMemory (method_response,
-                             length == -1 ? strlen (method_response) : length);
-       if (!doc)
-               return FALSE;
+                                 length == -1 ? strlen (method_response) : length);
+       if (!doc) {
+               g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                            "Failed to parse response XML");
+               goto fail;
+       }
 
        node = xmlDocGetRootElement (doc);
-       if (!node || strcmp ((const char *)node->name, "methodResponse") != 0)
+       if (!node || strcmp ((const char *)node->name, "methodResponse") != 0) {
+               g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                            "Missing 'methodResponse' node");
                goto fail;
+       }
 
        node = find_real_node (node->children);
-       if (!node)
+       if (!node) {
+               g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                            "'methodResponse' has no child");
                goto fail;
+       }
 
-       if (!strcmp ((const char *)node->name, "fault") && error) {
+       if (!strcmp ((const char *)node->name, "fault")) {
                int fault_code;
-               char *fault_string;
-               GValue fault_val;
-               GHashTable *fault_hash;
+               const char *fault_string;
+               const char *fault_sig = "a{sv}";
+               GVariant *fault_val;
 
                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);
+               if (!node || strcmp ((const char *)node->name, "value") != 0) {
+                       g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                                    "'fault' has no 'value' child");
                        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);
+
+               fault_val = parse_value (node, &fault_sig, error);
+               if (!fault_val)
                        goto fail;
-               }
 
+               if (!g_variant_lookup (fault_val, "faultCode", "i", &fault_code) ||
+                   !g_variant_lookup (fault_val, "faultString", "&s", &fault_string))  {
+                       g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                                    "'fault' missing 'faultCode' or 'faultString'");
+                       goto fail;
+               }
                g_set_error (error, SOUP_XMLRPC_FAULT,
-                            fault_code, "%s", fault_string);
-               g_value_unset (&fault_val);
+                            fault_code, "%s", fault_string);
+               g_variant_unref (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)
+               if (!node || strcmp ((const char *)node->name, "param") != 0) {
+                       g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                                    "'params' has no 'param' child");
                        goto fail;
+               }
                node = find_real_node (node->children);
-               if (!node || strcmp ((const char *)node->name, "value") != 0)
-                       goto fail;
-               if (!parse_value (node, value))
+               if (!node || strcmp ((const char *)node->name, "value") != 0) {
+                       g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                                    "'param' has no 'value' child");
                        goto fail;
-               success = TRUE;
+               }
+               value = parse_value (node, signature ? &signature : NULL, error);
        }
 
 fail:
-       xmlFreeDoc (doc);
-       return success;
+       if (doc)
+               xmlFreeDoc (doc);
+       return value ? g_variant_ref_sink (value) : NULL;
 }
 
 /**
- * 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.)
- *
- * Deprecated: Use soup_xmlrpc_parse_response() instead.
- * Return value: %TRUE if a return value was parsed, %FALSE if the
- * response was of the wrong type, or contained a fault.
- **/
-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;
-}
-
-
-GQuark
-soup_xmlrpc_error_quark (void)
+ * soup_xmlrpc_new_custom:
+ * @type: name for the type node
+ * @value: value for the type node
+ *
+ * Construct a special #GVariant used to serialize a &lt;@type&gt;
+ * node containing @value. See soup_xmlrpc_build_request().
+ *
+ * Returns: a floating #GVariant.
+ * Since: 2.52
+ */
+GVariant *
+soup_xmlrpc_new_custom (const char *type, const gchar *value)
 {
-       static GQuark error;
-       if (!error)
-               error = g_quark_from_static_string ("soup_xmlrpc_error_quark");
-       return error;
+       return g_variant_new ("(oss)", "/org/gnome/libsoup/ExtensionType",
+                             type, value);
 }
 
 /**
- * SOUP_XMLRPC_FAULT:
+ * soup_xmlrpc_new_datetime:
+ * @timestamp: a unix timestamp
  *
- * A #GError domain representing an XML-RPC fault code. Used with
- * #SoupXMLRPCFault (although servers may also return fault codes not
- * in that enumeration).
- */
-
-/**
- * SoupXMLRPCFault:
- * @SOUP_XMLRPC_FAULT_PARSE_ERROR_NOT_WELL_FORMED: request was not
- *   well-formed
- * @SOUP_XMLRPC_FAULT_PARSE_ERROR_UNSUPPORTED_ENCODING: request was in
- *   an unsupported encoding
- * @SOUP_XMLRPC_FAULT_PARSE_ERROR_INVALID_CHARACTER_FOR_ENCODING:
- *   request contained an invalid character
- * @SOUP_XMLRPC_FAULT_SERVER_ERROR_INVALID_XML_RPC: request was not
- *   valid XML-RPC
- * @SOUP_XMLRPC_FAULT_SERVER_ERROR_REQUESTED_METHOD_NOT_FOUND: method
- *   not found
- * @SOUP_XMLRPC_FAULT_SERVER_ERROR_INVALID_METHOD_PARAMETERS: invalid
- *   parameters
- * @SOUP_XMLRPC_FAULT_SERVER_ERROR_INTERNAL_XML_RPC_ERROR: internal
- *   error
- * @SOUP_XMLRPC_FAULT_APPLICATION_ERROR: start of reserved range for
- *   application error codes
- * @SOUP_XMLRPC_FAULT_SYSTEM_ERROR: start of reserved range for
- *   system error codes
- * @SOUP_XMLRPC_FAULT_TRANSPORT_ERROR: start of reserved range for
- *   transport error codes
- *
- * Pre-defined XML-RPC fault codes from <ulink
- * 
url="http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php";>http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php</ulink>.
- * These are an extension, not part of the XML-RPC spec; you can't
- * assume servers will use them.
+ * Construct a special #GVariant used to serialize a &lt;dateTime.iso8601&gt;
+ * node. See soup_xmlrpc_build_request().
+ *
+ * Returns: a floating #GVariant.
+ * Since: 2.52
  */
-
-GQuark
-soup_xmlrpc_fault_quark (void)
+GVariant *
+soup_xmlrpc_new_datetime (time_t timestamp)
 {
-       static GQuark error;
-       if (!error)
-               error = g_quark_from_static_string ("soup_xmlrpc_fault_quark");
-       return error;
-}
+       SoupDate *date;
+       GVariant *variant;
+       char *str;
 
-static xmlNode *
-find_real_node (xmlNode *node)
-{
-       while (node && (node->type == XML_COMMENT_NODE ||
-                       xmlIsBlankNode (node)))
-               node = node->next;
-       return node;
-}
+       date = soup_date_new_from_time_t (timestamp);
+       str = soup_date_to_string (date, SOUP_DATE_ISO8601_XMLRPC);
+       variant = soup_xmlrpc_new_custom ("dateTime.iso8601", str);
+       soup_date_free (date);
+       g_free (str);
 
-G_GNUC_END_IGNORE_DEPRECATIONS
+       return variant;
+}
diff --git a/libsoup/soup-xmlrpc.h b/libsoup/soup-xmlrpc.h
index c0bc6c1..d92582d 100644
--- a/libsoup/soup-xmlrpc.h
+++ b/libsoup/soup-xmlrpc.h
@@ -1,85 +1,62 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
 /*
- * Copyright (C) 2007 Red Hat, Inc.
+ * Copyright 2015 - Collabora Ltd.
  */
 
 #ifndef SOUP_XMLRPC_H
 #define SOUP_XMLRPC_H 1
 
 #include <libsoup/soup-types.h>
+#include <libsoup/soup-xmlrpc-gvalue.h>
 
 G_BEGIN_DECLS
 
 /* XML-RPC client */
-SOUP_DEPRECATED_IN_2_52_FOR(soup_xmlrpc_build_request)
-char        *soup_xmlrpc_build_method_call       (const char   *method_name,
-                                                 GValue       *params,
-                                                 int           n_params);
-SOUP_DEPRECATED_IN_2_52_FOR(soup_xmlrpc_message_new)
-SoupMessage *soup_xmlrpc_request_new             (const char   *uri,
-                                                 const char   *method_name,
-                                                 ...);
-SOUP_DEPRECATED_IN_2_52_FOR(soup_xmlrpc_parse_response)
-gboolean     soup_xmlrpc_parse_method_response   (const char   *method_response,
-                                                 int           length,
-                                                 GValue       *value,
-                                                 GError      **error);
-SOUP_DEPRECATED_IN_2_52_FOR(soup_xmlrpc_parse_response)
-gboolean     soup_xmlrpc_extract_method_response (const char   *method_response,
-                                                 int           length,
-                                                 GError      **error,
-                                                 GType         type,
-                                                 ...);
+char       *soup_xmlrpc_build_request  (const gchar *method_name,
+                                        GVariant    *params,
+                                        GError     **error);
+SoupMessage *soup_xmlrpc_message_new    (const char *uri,
+                                        const char *method_name,
+                                        GVariant    *params,
+                                        GError     **error);
+GVariant    *soup_xmlrpc_parse_response (const char *method_response,
+                                        int         length,
+                                        const char *signature,
+                                        GError     **error);
 
 /* XML-RPC server */
-SOUP_DEPRECATED_IN_2_52_FOR(soup_xmlrpc_parse_request_full)
-gboolean     soup_xmlrpc_parse_method_call       (const char   *method_call,
-                                                 int           length,
-                                                 char        **method_name,
-                                                 GValueArray **params);
-SOUP_DEPRECATED_IN_2_52_FOR(soup_xmlrpc_parse_request_full)
-gboolean     soup_xmlrpc_extract_method_call     (const char   *method_call,
-                                                 int           length,
-                                                 char        **method_name,
-                                                 ...);
-SOUP_DEPRECATED_IN_2_52_FOR(soup_xmlrpc_build_response)
-char        *soup_xmlrpc_build_method_response   (GValue       *value);
-SOUP_DEPRECATED_IN_2_52_FOR(soup_xmlrpc_message_set_response)
-void         soup_xmlrpc_set_response            (SoupMessage  *msg,
-                                                 GType         type,
-                                                 ...);
-SOUP_DEPRECATED_IN_2_52_FOR(soup_xmlrpc_message_set_fault)
-void         soup_xmlrpc_set_fault               (SoupMessage  *msg,
-                                                 int           fault_code,
-                                                 const char   *fault_format,
-                                                 ...) G_GNUC_PRINTF (3, 4);
-
-
-/* Errors */
-#define SOUP_XMLRPC_ERROR soup_xmlrpc_error_quark()
-GQuark soup_xmlrpc_error_quark (void);
-
-typedef enum {
-       SOUP_XMLRPC_ERROR_ARGUMENTS,
-       SOUP_XMLRPC_ERROR_RETVAL
-} SoupXMLRPCError;
-
-#define SOUP_XMLRPC_FAULT soup_xmlrpc_fault_quark()
-GQuark soup_xmlrpc_fault_quark (void);
-
-typedef enum {
-       SOUP_XMLRPC_FAULT_PARSE_ERROR_NOT_WELL_FORMED = -32700,
-       SOUP_XMLRPC_FAULT_PARSE_ERROR_UNSUPPORTED_ENCODING = -32701,
-       SOUP_XMLRPC_FAULT_PARSE_ERROR_INVALID_CHARACTER_FOR_ENCODING = -32702,
-       SOUP_XMLRPC_FAULT_SERVER_ERROR_INVALID_XML_RPC = -32600,
-       SOUP_XMLRPC_FAULT_SERVER_ERROR_REQUESTED_METHOD_NOT_FOUND = -32601,
-       SOUP_XMLRPC_FAULT_SERVER_ERROR_INVALID_METHOD_PARAMETERS = -32602,
-       SOUP_XMLRPC_FAULT_SERVER_ERROR_INTERNAL_XML_RPC_ERROR = -32603,
-       SOUP_XMLRPC_FAULT_APPLICATION_ERROR = -32500,
-       SOUP_XMLRPC_FAULT_SYSTEM_ERROR = -32400,
-       SOUP_XMLRPC_FAULT_TRANSPORT_ERROR = -32300
-} SoupXMLRPCFault;
+typedef struct _SoupXMLRPCParams SoupXMLRPCParams;
+void         soup_xmlrpc_params_free          (SoupXMLRPCParams  *self);
+GVariant    *soup_xmlrpc_params_parse         (SoupXMLRPCParams  *self,
+                                              const char       *signature,
+                                              GError           **error);
+char       *soup_xmlrpc_parse_request        (const gchar       *method_call,
+                                              int               length,
+                                              SoupXMLRPCParams **params,
+                                              GError           **error);
+char       *soup_xmlrpc_parse_request_full   (const gchar       *method_call,
+                                              int               length,
+                                              const char       *signature,
+                                              GVariant         **parameters,
+                                              GError           **error);
+char       *soup_xmlrpc_build_response       (GVariant          *value,
+                                              GError           **error);
+char       *soup_xmlrpc_build_fault          (int               fault_code,
+                                              const char       *fault_format,
+                                              ...) G_GNUC_PRINTF (2, 3);
+gboolean     soup_xmlrpc_message_set_response (SoupMessage       *msg,
+                                              GVariant          *value,
+                                              GError           **error);
+void         soup_xmlrpc_message_set_fault    (SoupMessage       *msg,
+                                              int               fault_code,
+                                              const char       *fault_format,
+                                              ...) G_GNUC_PRINTF (3, 4);
+
+/* Utils */
+GVariant *soup_xmlrpc_new_custom   (const char *type,
+                                   const char *value);
+GVariant *soup_xmlrpc_new_datetime (time_t       timestamp);
 
 G_END_DECLS
 
-#endif /* SOUP_XMLRPC_H */
+#endif /* SOUP_XMLRPC_VARIANT_H */
diff --git a/libsoup/soup.h b/libsoup/soup.h
index 8a6bef8..1e56bcd 100644
--- a/libsoup/soup.h
+++ b/libsoup/soup.h
@@ -53,7 +53,7 @@ extern "C" {
 #include <libsoup/soup-websocket.h>
 #include <libsoup/soup-websocket-connection.h>
 #include <libsoup/soup-xmlrpc.h>
-#include <libsoup/soup-xmlrpc-variant.h>
+#include <libsoup/soup-xmlrpc-gvalue.h>
 
 #ifdef __cplusplus
 }
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 83f9508..ee78b81 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -44,7 +44,7 @@ test_programs =                       \
        websocket-test          \
        xmlrpc-server-test      \
        xmlrpc-test             \
-       xmlrpc-variant-test
+       xmlrpc-gvalue-test
 
 test_extra_programs =          \
        ntlm-test-helper        \
diff --git a/tests/xmlrpc-gvalue-test.c b/tests/xmlrpc-gvalue-test.c
new file mode 100644
index 0000000..cf9cb91
--- /dev/null
+++ b/tests/xmlrpc-gvalue-test.c
@@ -0,0 +1,513 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2001-2003, Ximian, Inc.
+ */
+
+#include "test-utils.h"
+
+#ifdef G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+#endif
+
+static SoupSession *session;
+static const char *default_uri = "http://127.0.0.1:47524/xmlrpc-server.php";;
+static const char *uri = NULL;
+static gboolean server_test = FALSE;
+
+#ifdef HAVE_PHP_XMLRPC
+#define SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER
+#else
+#define SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER                             \
+       G_STMT_START {                                                  \
+               if (!server_test) {                                     \
+                       g_test_skip ("php-xmlrpc is not available");    \
+                       return;                                         \
+               }                                                       \
+       } G_STMT_END
+#endif
+
+static const char *const value_type[] = {
+       "BAD",
+       "int",
+       "boolean",
+       "string",
+       "double",
+       "datetime",
+       "base64",
+       "struct",
+       "array"
+};
+
+static gboolean
+send_xmlrpc (const char *body, GValue *retval)
+{
+       SoupMessage *msg;
+       GError *err = NULL;
+
+       msg = soup_message_new ("POST", uri);
+       soup_message_set_request (msg, "text/xml", SOUP_MEMORY_COPY,
+                                 body, strlen (body));
+       soup_session_send_message (session, msg);
+
+       soup_test_assert_message_status (msg, SOUP_STATUS_OK);
+
+       if (!soup_xmlrpc_parse_method_response (msg->response_body->data,
+                                               msg->response_body->length,
+                                               retval, &err)) {
+               if (err) {
+                       soup_test_assert (FALSE, "FAULT: %d %s\n", err->code, err->message);
+                       g_error_free (err);
+               } else
+                       soup_test_assert (FALSE, "ERROR: could not parse response\n");
+               g_object_unref (msg);
+               return FALSE;
+       }
+       g_object_unref (msg);
+
+       return TRUE;
+}
+
+static gboolean
+do_xmlrpc (const char *method, GValue *retval, ...)
+{
+       va_list args;
+       GValueArray *params;
+       char *body;
+       gboolean ret;
+
+       va_start (args, retval);
+       params = soup_value_array_from_args (args);
+       va_end (args);
+
+       body = soup_xmlrpc_build_method_call (method, params->values,
+                                             params->n_values);
+       g_value_array_free (params);
+       if (!body)
+               return FALSE;
+
+       ret = send_xmlrpc (body, retval);
+       g_free (body);
+
+       return ret;
+}
+
+static gboolean
+check_xmlrpc (GValue *value, GType type, ...)
+{
+       va_list args;
+
+       if (!G_VALUE_HOLDS (value, type)) {
+               g_assert_true (G_VALUE_HOLDS (value, type));
+               return FALSE;
+       }
+
+       va_start (args, type);
+       SOUP_VALUE_GETV (value, type, args);
+       va_end (args);
+       return TRUE;
+}
+
+static void
+test_sum (void)
+{
+       GValueArray *ints;
+       int i, val, sum, result;
+       GValue retval;
+       gboolean ok;
+
+       SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER;
+
+       debug_printf (2, "sum (array of int -> int): ");
+
+       ints = g_value_array_new (10);
+       for (i = sum = 0; i < 10; i++) {
+               val = g_random_int_range (0, 100);
+               debug_printf (2, "%s%d", i == 0 ? "[" : ", ", val);
+               soup_value_array_append (ints, G_TYPE_INT, val);
+               sum += val;
+       }
+       debug_printf (2, "] -> ");
+
+       ok = (do_xmlrpc ("sum", &retval,
+                       G_TYPE_VALUE_ARRAY, ints,
+                       G_TYPE_INVALID) &&
+             check_xmlrpc (&retval, G_TYPE_INT, &result));
+       g_value_array_free (ints);
+
+       if (!ok)
+               return;
+
+       debug_printf (2, "%d\n", result);
+       g_assert_cmpint (result, ==, sum);
+}
+
+static void
+test_countBools (void)
+{
+       GValueArray *bools;
+       int i, trues, falses;
+       GValue retval;
+       int ret_trues, ret_falses;
+       gboolean val, ok;
+       GHashTable *result;
+
+       SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER;
+
+       debug_printf (2, "countBools (array of boolean -> struct of ints): ");
+
+       bools = g_value_array_new (10);
+       for (i = trues = falses = 0; i < 10; i++) {
+               val = g_random_boolean ();
+               debug_printf (2, "%s%c", i == 0 ? "[" : ", ", val ? 'T' : 'F');
+               soup_value_array_append (bools, G_TYPE_BOOLEAN, val);
+               if (val)
+                       trues++;
+               else
+                       falses++;
+       }
+       debug_printf (2, "] -> ");
+
+       ok = (do_xmlrpc ("countBools", &retval,
+                        G_TYPE_VALUE_ARRAY, bools,
+                        G_TYPE_INVALID) &&
+             check_xmlrpc (&retval, G_TYPE_HASH_TABLE, &result));
+       g_value_array_free (bools);
+       if (!ok)
+               return;
+
+       g_assert_true (soup_value_hash_lookup (result, "true", G_TYPE_INT, &ret_trues));
+       g_assert_true (soup_value_hash_lookup (result, "false", G_TYPE_INT, &ret_falses));
+
+       g_hash_table_destroy (result);
+
+       debug_printf (2, "{ true: %d, false: %d }\n", ret_trues, ret_falses);
+       g_assert_cmpint (trues, ==, ret_trues);
+       g_assert_cmpint (falses, ==, ret_falses);
+}
+
+static void
+test_md5sum (void)
+{
+       GByteArray *data, *result;
+       int i;
+       GChecksum *checksum;
+       guchar digest[16];
+       gsize digest_len = sizeof (digest);
+       GValue retval;
+       gboolean ok;
+
+       SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER;
+
+       debug_printf (2, "md5sum (base64 -> base64)\n");
+
+       data = g_byte_array_new ();
+       g_byte_array_set_size (data, 256);
+       for (i = 0; i < data->len; i++)
+               data->data[i] = (char)(g_random_int_range (0, 256));
+
+       checksum = g_checksum_new (G_CHECKSUM_MD5);
+       g_checksum_update (checksum, data->data, data->len);
+       g_checksum_get_digest (checksum, digest, &digest_len);
+       g_checksum_free (checksum);
+
+       ok = (do_xmlrpc ("md5sum", &retval,
+                        SOUP_TYPE_BYTE_ARRAY, data,
+                        G_TYPE_INVALID) &&
+             check_xmlrpc (&retval, SOUP_TYPE_BYTE_ARRAY, &result));
+       g_byte_array_free (data, TRUE);
+       if (!ok)
+               return;
+
+       soup_assert_cmpmem (result->data, result->len,
+                           digest, digest_len);
+       g_byte_array_free (result, TRUE);
+}
+
+static void
+test_dateChange (void)
+{
+       GHashTable *structval;
+       SoupDate *date, *result;
+       char *timestamp;
+       GValue retval;
+       gboolean ok;
+
+       SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER;
+
+       debug_printf (2, "dateChange (date, struct of ints -> time)\n");
+
+       date = soup_date_new (1970 + (g_random_int_range (0, 50)),
+                             1 + g_random_int_range (0, 12),
+                             1 + g_random_int_range (0, 28),
+                             g_random_int_range (0, 24),
+                             g_random_int_range (0, 60),
+                             g_random_int_range (0, 60));
+       if (debug_level >= 2) {
+               timestamp = soup_date_to_string (date, SOUP_DATE_ISO8601_XMLRPC);
+               debug_printf (2, "date: %s, {", timestamp);
+               g_free (timestamp);
+       }
+
+       structval = soup_value_hash_new ();
+
+#define MAYBE (g_random_int_range (0, 3) != 0)
+
+       if (MAYBE) {
+               date->year = 1970 + (g_random_int_range (0, 50));
+               debug_printf (2, "tm_year: %d, ", date->year - 1900);
+               soup_value_hash_insert (structval, "tm_year",
+                                       G_TYPE_INT, date->year - 1900);
+       }
+       if (MAYBE) {
+               date->month = 1 + g_random_int_range (0, 12);
+               debug_printf (2, "tm_mon: %d, ", date->month - 1);
+               soup_value_hash_insert (structval, "tm_mon",
+                                       G_TYPE_INT, date->month - 1);
+       }
+       if (MAYBE) {
+               date->day = 1 + g_random_int_range (0, 28);
+               debug_printf (2, "tm_mday: %d, ", date->day);
+               soup_value_hash_insert (structval, "tm_mday",
+                                       G_TYPE_INT, date->day);
+       }
+       if (MAYBE) {
+               date->hour = g_random_int_range (0, 24);
+               debug_printf (2, "tm_hour: %d, ", date->hour);
+               soup_value_hash_insert (structval, "tm_hour",
+                                       G_TYPE_INT, date->hour);
+       }
+       if (MAYBE) {
+               date->minute = g_random_int_range (0, 60);
+               debug_printf (2, "tm_min: %d, ", date->minute);
+               soup_value_hash_insert (structval, "tm_min",
+                                       G_TYPE_INT, date->minute);
+       }
+       if (MAYBE) {
+               date->second = g_random_int_range (0, 60);
+               debug_printf (2, "tm_sec: %d, ", date->second);
+               soup_value_hash_insert (structval, "tm_sec",
+                                       G_TYPE_INT, date->second);
+       }
+
+       debug_printf (2, "} -> ");
+
+       ok = (do_xmlrpc ("dateChange", &retval,
+                        SOUP_TYPE_DATE, date,
+                        G_TYPE_HASH_TABLE, structval,
+                        G_TYPE_INVALID) &&
+             check_xmlrpc (&retval, SOUP_TYPE_DATE, &result));
+       g_hash_table_destroy (structval);
+       if (!ok) {
+               soup_date_free (date);
+               return;
+       }
+
+       if (debug_level >= 2) {
+               timestamp = soup_date_to_string (result, SOUP_DATE_ISO8601_XMLRPC);
+               debug_printf (2, "%s\n", timestamp);
+               g_free (timestamp);
+       }
+
+       g_assert_cmpint (date->year,   ==, result->year);
+       g_assert_cmpint (date->month,  ==, result->month);
+       g_assert_cmpint (date->day,    ==, result->day);
+       g_assert_cmpint (date->hour,   ==, result->hour);
+       g_assert_cmpint (date->minute, ==, result->minute);
+       g_assert_cmpint (date->second, ==, result->second);
+
+       soup_date_free (date);
+       soup_date_free (result);
+}
+
+static const char *const echo_strings[] = {
+       "This is a test",
+       "& so is this",
+       "and so is <this>",
+       "&amp; so is &lt;this&gt;"
+};
+#define N_ECHO_STRINGS G_N_ELEMENTS (echo_strings)
+
+static void
+test_echo (void)
+{
+       GValueArray *originals, *echoes;
+       GValue retval;
+       int i;
+
+       SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER;
+
+       debug_printf (2, "echo (array of string -> array of string):\n");
+
+       originals = g_value_array_new (N_ECHO_STRINGS);
+       for (i = 0; i < N_ECHO_STRINGS; i++) {
+               soup_value_array_append (originals, G_TYPE_STRING, echo_strings[i]);
+               debug_printf (2, "%s\"%s\"", i == 0 ? "[" : ", ", echo_strings[i]);
+       }
+       debug_printf (2, "] -> ");
+
+       if (!(do_xmlrpc ("echo", &retval,
+                        G_TYPE_VALUE_ARRAY, originals,
+                        G_TYPE_INVALID) &&
+             check_xmlrpc (&retval, G_TYPE_VALUE_ARRAY, &echoes))) {
+               g_value_array_free (originals);
+               return;
+       }
+       g_value_array_free (originals);
+
+       if (debug_level >= 2) {
+               for (i = 0; i < echoes->n_values; i++) {
+                       debug_printf (2, "%s\"%s\"", i == 0 ? "[" : ", ",
+                                     g_value_get_string (&echoes->values[i]));
+               }
+               debug_printf (2, "]\n");
+       }
+
+       g_assert_cmpint (echoes->n_values, ==, N_ECHO_STRINGS);
+
+       for (i = 0; i < echoes->n_values; i++)
+               g_assert_cmpstr (echo_strings[i], ==, g_value_get_string (&echoes->values[i]));
+
+       g_value_array_free (echoes);
+}
+
+static void
+test_ping (gconstpointer include_params)
+{
+       GValueArray *params;
+       GValue retval;
+       char *request;
+       char *out;
+       gboolean ret;
+
+       g_test_bug ("671661");
+
+       SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER;
+
+       debug_printf (2, "ping (void (%s) -> string)\n",
+                     include_params ? "empty <params>" : "no <params>");
+
+       params = soup_value_array_new ();
+       request = soup_xmlrpc_build_method_call ("ping", params->values,
+                                                params->n_values);
+       g_value_array_free (params);
+       if (!request)
+               return;
+
+       if (!include_params) {
+               char *params, *end;
+
+               params = strstr (request, "<params/>");
+               if (!params) {
+                       soup_test_assert (FALSE, "ERROR: XML did not contain <params/>!");
+                       return;
+               }
+               end = params + strlen ("<params/>");
+               memmove (params, end, strlen (end) + 1);
+       }
+
+       ret = send_xmlrpc (request, &retval);
+       g_free (request);
+
+       if (!ret || !check_xmlrpc (&retval, G_TYPE_STRING, &out))
+               return;
+
+       g_assert_cmpstr (out, ==, "pong");
+
+       g_free (out);
+}
+
+static void
+do_bad_xmlrpc (const char *body)
+{
+       SoupMessage *msg;
+       GError *err = NULL;
+       GValue retval;
+
+       msg = soup_message_new ("POST", uri);
+       soup_message_set_request (msg, "text/xml", SOUP_MEMORY_COPY,
+                                 body, strlen (body));
+       soup_session_send_message (session, msg);
+
+       soup_test_assert_message_status (msg, SOUP_STATUS_OK);
+
+       if (!soup_xmlrpc_parse_method_response (msg->response_body->data,
+                                               msg->response_body->length,
+                                               &retval, &err)) {
+               if (err) {
+                       debug_printf (1, "FAULT: %d %s (OK!)\n",
+                                     err->code, err->message);
+                       g_error_free (err);
+                       g_object_unref (msg);
+                       return;
+               } else
+                       soup_test_assert (FALSE, "ERROR: could not parse response\n");
+       } else
+               soup_test_assert (FALSE, "Unexpectedly got successful response!\n");
+
+       g_object_unref (msg);
+}
+
+static void
+test_fault_malformed (void)
+{
+       SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER;
+
+       do_bad_xmlrpc ("<methodCall/>");
+}
+
+static void
+test_fault_method (void)
+{
+       SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER;
+
+       do_bad_xmlrpc 
("<methodCall><methodName>no_such_method</methodName><params><param><value><int>1</int></value></param></params></methodCall>");
+}
+
+static void
+test_fault_args (void)
+{
+       SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER;
+
+       do_bad_xmlrpc 
("<methodCall><methodName>sum</methodName><params><param><value><int>1</int></value></param></params></methodCall>");
+}
+
+static GOptionEntry xmlrpc_entries[] = {
+        { "uri", 'U', 0, G_OPTION_ARG_STRING, &uri,
+          "Alternate URI for server", NULL },
+        { "server-test", 'S', 0, G_OPTION_ARG_NONE, &server_test,
+          "If this is being run from xmlrpc-server-test", NULL },
+        { NULL }
+};
+
+int
+main (int argc, char **argv)
+{
+       int ret;
+
+       test_init (argc, argv, xmlrpc_entries);
+
+       if (!uri && !server_test) {
+               apache_init ();
+               uri = default_uri;
+       }
+
+       session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
+
+       g_test_add_func ("/xmlrpc/sum", test_sum);
+       g_test_add_func ("/xmlrpc/countBools", test_countBools);
+       g_test_add_func ("/xmlrpc/md5sum", test_md5sum);
+       g_test_add_func ("/xmlrpc/dateChange", test_dateChange);
+       g_test_add_func ("/xmlrpc/echo", test_echo);
+       g_test_add_data_func ("/xmlrpc/ping/empty-params", GINT_TO_POINTER (TRUE), test_ping);
+       g_test_add_data_func ("/xmlrpc/ping/no-params", GINT_TO_POINTER (FALSE), test_ping);
+       g_test_add_func ("/xmlrpc/fault/malformed", test_fault_malformed);
+       g_test_add_func ("/xmlrpc/fault/method", test_fault_method);
+       g_test_add_func ("/xmlrpc/fault/args", test_fault_args);
+
+       ret = g_test_run ();
+
+       soup_test_session_abort_unref (session);
+
+       test_cleanup ();
+       return ret;
+}
diff --git a/tests/xmlrpc-test.c b/tests/xmlrpc-test.c
index cf9cb91..b878d13 100644
--- a/tests/xmlrpc-test.c
+++ b/tests/xmlrpc-test.c
@@ -1,513 +1,269 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
 /*
- * Copyright (C) 2001-2003, Ximian, Inc.
+ * Copyright 2015, Collabora ltd.
  */
 
 #include "test-utils.h"
 
-#ifdef G_GNUC_BEGIN_IGNORE_DEPRECATIONS
-G_GNUC_BEGIN_IGNORE_DEPRECATIONS
-#endif
-
-static SoupSession *session;
-static const char *default_uri = "http://127.0.0.1:47524/xmlrpc-server.php";;
-static const char *uri = NULL;
-static gboolean server_test = FALSE;
-
-#ifdef HAVE_PHP_XMLRPC
-#define SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER
-#else
-#define SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER                             \
-       G_STMT_START {                                                  \
-               if (!server_test) {                                     \
-                       g_test_skip ("php-xmlrpc is not available");    \
-                       return;                                         \
-               }                                                       \
-       } G_STMT_END
-#endif
-
-static const char *const value_type[] = {
-       "BAD",
-       "int",
-       "boolean",
-       "string",
-       "double",
-       "datetime",
-       "base64",
-       "struct",
-       "array"
-};
-
-static gboolean
-send_xmlrpc (const char *body, GValue *retval)
-{
-       SoupMessage *msg;
-       GError *err = NULL;
-
-       msg = soup_message_new ("POST", uri);
-       soup_message_set_request (msg, "text/xml", SOUP_MEMORY_COPY,
-                                 body, strlen (body));
-       soup_session_send_message (session, msg);
-
-       soup_test_assert_message_status (msg, SOUP_STATUS_OK);
-
-       if (!soup_xmlrpc_parse_method_response (msg->response_body->data,
-                                               msg->response_body->length,
-                                               retval, &err)) {
-               if (err) {
-                       soup_test_assert (FALSE, "FAULT: %d %s\n", err->code, err->message);
-                       g_error_free (err);
-               } else
-                       soup_test_assert (FALSE, "ERROR: could not parse response\n");
-               g_object_unref (msg);
-               return FALSE;
-       }
-       g_object_unref (msg);
-
-       return TRUE;
-}
-
-static gboolean
-do_xmlrpc (const char *method, GValue *retval, ...)
-{
-       va_list args;
-       GValueArray *params;
-       char *body;
-       gboolean ret;
-
-       va_start (args, retval);
-       params = soup_value_array_from_args (args);
-       va_end (args);
-
-       body = soup_xmlrpc_build_method_call (method, params->values,
-                                             params->n_values);
-       g_value_array_free (params);
-       if (!body)
-               return FALSE;
-
-       ret = send_xmlrpc (body, retval);
-       g_free (body);
-
-       return ret;
-}
-
-static gboolean
-check_xmlrpc (GValue *value, GType type, ...)
-{
-       va_list args;
-
-       if (!G_VALUE_HOLDS (value, type)) {
-               g_assert_true (G_VALUE_HOLDS (value, type));
-               return FALSE;
-       }
-
-       va_start (args, type);
-       SOUP_VALUE_GETV (value, type, args);
-       va_end (args);
-       return TRUE;
-}
+#define BODY_PREFIX \
+       "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" \
+       "<methodCall><methodName>MyMethod</methodName>"
+#define BODY_SUFFIX \
+       "</methodCall>\n"
 
 static void
-test_sum (void)
+verify_serialization (GVariant    *value,
+                     const char *expected_params)
 {
-       GValueArray *ints;
-       int i, val, sum, result;
-       GValue retval;
-       gboolean ok;
-
-       SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER;
-
-       debug_printf (2, "sum (array of int -> int): ");
-
-       ints = g_value_array_new (10);
-       for (i = sum = 0; i < 10; i++) {
-               val = g_random_int_range (0, 100);
-               debug_printf (2, "%s%d", i == 0 ? "[" : ", ", val);
-               soup_value_array_append (ints, G_TYPE_INT, val);
-               sum += val;
-       }
-       debug_printf (2, "] -> ");
-
-       ok = (do_xmlrpc ("sum", &retval,
-                       G_TYPE_VALUE_ARRAY, ints,
-                       G_TYPE_INVALID) &&
-             check_xmlrpc (&retval, G_TYPE_INT, &result));
-       g_value_array_free (ints);
-
-       if (!ok)
-               return;
-
-       debug_printf (2, "%d\n", result);
-       g_assert_cmpint (result, ==, sum);
-}
-
-static void
-test_countBools (void)
-{
-       GValueArray *bools;
-       int i, trues, falses;
-       GValue retval;
-       int ret_trues, ret_falses;
-       gboolean val, ok;
-       GHashTable *result;
-
-       SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER;
-
-       debug_printf (2, "countBools (array of boolean -> struct of ints): ");
-
-       bools = g_value_array_new (10);
-       for (i = trues = falses = 0; i < 10; i++) {
-               val = g_random_boolean ();
-               debug_printf (2, "%s%c", i == 0 ? "[" : ", ", val ? 'T' : 'F');
-               soup_value_array_append (bools, G_TYPE_BOOLEAN, val);
-               if (val)
-                       trues++;
-               else
-                       falses++;
-       }
-       debug_printf (2, "] -> ");
-
-       ok = (do_xmlrpc ("countBools", &retval,
-                        G_TYPE_VALUE_ARRAY, bools,
-                        G_TYPE_INVALID) &&
-             check_xmlrpc (&retval, G_TYPE_HASH_TABLE, &result));
-       g_value_array_free (bools);
-       if (!ok)
-               return;
-
-       g_assert_true (soup_value_hash_lookup (result, "true", G_TYPE_INT, &ret_trues));
-       g_assert_true (soup_value_hash_lookup (result, "false", G_TYPE_INT, &ret_falses));
-
-       g_hash_table_destroy (result);
-
-       debug_printf (2, "{ true: %d, false: %d }\n", ret_trues, ret_falses);
-       g_assert_cmpint (trues, ==, ret_trues);
-       g_assert_cmpint (falses, ==, ret_falses);
-}
-
-static void
-test_md5sum (void)
-{
-       GByteArray *data, *result;
-       int i;
-       GChecksum *checksum;
-       guchar digest[16];
-       gsize digest_len = sizeof (digest);
-       GValue retval;
-       gboolean ok;
-
-       SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER;
+       char *debug;
+       char *body;
+       char *params;
+       GError *error = NULL;
 
-       debug_printf (2, "md5sum (base64 -> base64)\n");
+       debug = g_variant_print (value, TRUE);
 
-       data = g_byte_array_new ();
-       g_byte_array_set_size (data, 256);
-       for (i = 0; i < data->len; i++)
-               data->data[i] = (char)(g_random_int_range (0, 256));
+       body = soup_xmlrpc_build_request ("MyMethod", value, &error);
+       g_assert_no_error (error);
+       g_assert (g_str_has_prefix (body, BODY_PREFIX));
+       g_assert (g_str_has_suffix (body, BODY_SUFFIX));
 
-       checksum = g_checksum_new (G_CHECKSUM_MD5);
-       g_checksum_update (checksum, data->data, data->len);
-       g_checksum_get_digest (checksum, digest, &digest_len);
-       g_checksum_free (checksum);
+       params = g_strndup (body + strlen (BODY_PREFIX),
+                           strlen (body) - strlen (BODY_PREFIX)
+                                         - strlen (BODY_SUFFIX));
 
-       ok = (do_xmlrpc ("md5sum", &retval,
-                        SOUP_TYPE_BYTE_ARRAY, data,
-                        G_TYPE_INVALID) &&
-             check_xmlrpc (&retval, SOUP_TYPE_BYTE_ARRAY, &result));
-       g_byte_array_free (data, TRUE);
-       if (!ok)
-               return;
+       if (!g_str_equal (params, expected_params))
+               g_error ("Failed to serialize '%s':\n"
+                        "  expected: %s\n"
+                        "  got:      %s\n",
+                        debug, expected_params, params);
 
-       soup_assert_cmpmem (result->data, result->len,
-                           digest, digest_len);
-       g_byte_array_free (result, TRUE);
+       g_free (params);
+       g_free (body);
+       g_free (debug);
 }
 
 static void
-test_dateChange (void)
+verify_serialization_fail (GVariant *value)
 {
-       GHashTable *structval;
-       SoupDate *date, *result;
-       char *timestamp;
-       GValue retval;
-       gboolean ok;
-
-       SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER;
-
-       debug_printf (2, "dateChange (date, struct of ints -> time)\n");
-
-       date = soup_date_new (1970 + (g_random_int_range (0, 50)),
-                             1 + g_random_int_range (0, 12),
-                             1 + g_random_int_range (0, 28),
-                             g_random_int_range (0, 24),
-                             g_random_int_range (0, 60),
-                             g_random_int_range (0, 60));
-       if (debug_level >= 2) {
-               timestamp = soup_date_to_string (date, SOUP_DATE_ISO8601_XMLRPC);
-               debug_printf (2, "date: %s, {", timestamp);
-               g_free (timestamp);
-       }
-
-       structval = soup_value_hash_new ();
-
-#define MAYBE (g_random_int_range (0, 3) != 0)
-
-       if (MAYBE) {
-               date->year = 1970 + (g_random_int_range (0, 50));
-               debug_printf (2, "tm_year: %d, ", date->year - 1900);
-               soup_value_hash_insert (structval, "tm_year",
-                                       G_TYPE_INT, date->year - 1900);
-       }
-       if (MAYBE) {
-               date->month = 1 + g_random_int_range (0, 12);
-               debug_printf (2, "tm_mon: %d, ", date->month - 1);
-               soup_value_hash_insert (structval, "tm_mon",
-                                       G_TYPE_INT, date->month - 1);
-       }
-       if (MAYBE) {
-               date->day = 1 + g_random_int_range (0, 28);
-               debug_printf (2, "tm_mday: %d, ", date->day);
-               soup_value_hash_insert (structval, "tm_mday",
-                                       G_TYPE_INT, date->day);
-       }
-       if (MAYBE) {
-               date->hour = g_random_int_range (0, 24);
-               debug_printf (2, "tm_hour: %d, ", date->hour);
-               soup_value_hash_insert (structval, "tm_hour",
-                                       G_TYPE_INT, date->hour);
-       }
-       if (MAYBE) {
-               date->minute = g_random_int_range (0, 60);
-               debug_printf (2, "tm_min: %d, ", date->minute);
-               soup_value_hash_insert (structval, "tm_min",
-                                       G_TYPE_INT, date->minute);
-       }
-       if (MAYBE) {
-               date->second = g_random_int_range (0, 60);
-               debug_printf (2, "tm_sec: %d, ", date->second);
-               soup_value_hash_insert (structval, "tm_sec",
-                                       G_TYPE_INT, date->second);
-       }
-
-       debug_printf (2, "} -> ");
-
-       ok = (do_xmlrpc ("dateChange", &retval,
-                        SOUP_TYPE_DATE, date,
-                        G_TYPE_HASH_TABLE, structval,
-                        G_TYPE_INVALID) &&
-             check_xmlrpc (&retval, SOUP_TYPE_DATE, &result));
-       g_hash_table_destroy (structval);
-       if (!ok) {
-               soup_date_free (date);
-               return;
-       }
-
-       if (debug_level >= 2) {
-               timestamp = soup_date_to_string (result, SOUP_DATE_ISO8601_XMLRPC);
-               debug_printf (2, "%s\n", timestamp);
-               g_free (timestamp);
-       }
-
-       g_assert_cmpint (date->year,   ==, result->year);
-       g_assert_cmpint (date->month,  ==, result->month);
-       g_assert_cmpint (date->day,    ==, result->day);
-       g_assert_cmpint (date->hour,   ==, result->hour);
-       g_assert_cmpint (date->minute, ==, result->minute);
-       g_assert_cmpint (date->second, ==, result->second);
+       char *body;
+       GError *error = NULL;
 
-       soup_date_free (date);
-       soup_date_free (result);
+       body = soup_xmlrpc_build_request ("MyMethod", value, &error);
+       g_assert (body == NULL);
+       g_assert (error != NULL);
 }
 
-static const char *const echo_strings[] = {
-       "This is a test",
-       "& so is this",
-       "and so is <this>",
-       "&amp; so is &lt;this&gt;"
-};
-#define N_ECHO_STRINGS G_N_ELEMENTS (echo_strings)
-
 static void
-test_echo (void)
+test_serializer (void)
 {
-       GValueArray *originals, *echoes;
-       GValue retval;
-       int i;
-
-       SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER;
-
-       debug_printf (2, "echo (array of string -> array of string):\n");
-
-       originals = g_value_array_new (N_ECHO_STRINGS);
-       for (i = 0; i < N_ECHO_STRINGS; i++) {
-               soup_value_array_append (originals, G_TYPE_STRING, echo_strings[i]);
-               debug_printf (2, "%s\"%s\"", i == 0 ? "[" : ", ", echo_strings[i]);
-       }
-       debug_printf (2, "] -> ");
-
-       if (!(do_xmlrpc ("echo", &retval,
-                        G_TYPE_VALUE_ARRAY, originals,
-                        G_TYPE_INVALID) &&
-             check_xmlrpc (&retval, G_TYPE_VALUE_ARRAY, &echoes))) {
-               g_value_array_free (originals);
-               return;
-       }
-       g_value_array_free (originals);
-
-       if (debug_level >= 2) {
-               for (i = 0; i < echoes->n_values; i++) {
-                       debug_printf (2, "%s\"%s\"", i == 0 ? "[" : ", ",
-                                     g_value_get_string (&echoes->values[i]));
-               }
-               debug_printf (2, "]\n");
-       }
-
-       g_assert_cmpint (echoes->n_values, ==, N_ECHO_STRINGS);
-
-       for (i = 0; i < echoes->n_values; i++)
-               g_assert_cmpstr (echo_strings[i], ==, g_value_get_string (&echoes->values[i]));
-
-       g_value_array_free (echoes);
+       verify_serialization (g_variant_new_parsed ("()"),
+               "<params/>");
+       verify_serialization (g_variant_new_parsed ("(1, 2)"),
+               "<params>"
+               "<param><value><int>1</int></value></param>"
+               "<param><value><int>2</int></value></param>"
+               "</params>");
+       verify_serialization (g_variant_new_parsed ("((1, 2),)"),
+               "<params><param><value><array><data>"
+               "<value><int>1</int></value>"
+               "<value><int>2</int></value>"
+               "</data></array></value></param></params>");
+       verify_serialization (g_variant_new_parsed ("({'one', 1},)"),
+               "<params><param><value><struct>"
+               "<member><name>one</name><value><int>1</int></value></member>"
+               "</struct></value></param></params>");
+       verify_serialization (g_variant_new_parsed ("([{'one', 1},{'two', 2}],)"),
+               "<params><param><value><struct>"
+               "<member><name>one</name><value><int>1</int></value></member>"
+               "<member><name>two</name><value><int>2</int></value></member>"
+               "</struct></value></param></params>");
+       verify_serialization (g_variant_new ("(^ay)", "bytestring"),
+               "<params><param>"
+               "<value><base64>Ynl0ZXN0cmluZwA=</base64></value>"
+               "</param></params>");
+       verify_serialization (g_variant_new ("(y)", 42),
+               "<params>"
+               "<param><value><int>42</int></value></param>"
+               "</params>");
+       verify_serialization (g_variant_new ("(@*)", soup_xmlrpc_new_datetime (1434161309)),
+               "<params>"
+               "<param><value><dateTime.iso8601>20150613T02:08:29</dateTime.iso8601></value></param>"
+               "</params>");
+       verify_serialization (g_variant_new ("(s)", "<>&"),
+               "<params>"
+               "<param><value><string>&lt;&gt;&amp;</string></value></param>"
+               "</params>");
+       verify_serialization (g_variant_new ("(u)", 0),
+               "<params>"
+               "<param><value><i8>0</i8></value></param>"
+               "</params>");
+
+       verify_serialization_fail (g_variant_new_parsed ("1"));
+       verify_serialization_fail (g_variant_new_parsed ("({1, 2},)"));
+       verify_serialization_fail (g_variant_new ("(mi)", NULL));
+       verify_serialization_fail (g_variant_new ("(t)", 0));
 }
 
 static void
-test_ping (gconstpointer include_params)
+verify_deserialization (GVariant *expected_variant,
+                       const char *signature,
+                       const char *params)
 {
-       GValueArray *params;
-       GValue retval;
-       char *request;
-       char *out;
-       gboolean ret;
-
-       g_test_bug ("671661");
-
-       SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER;
-
-       debug_printf (2, "ping (void (%s) -> string)\n",
-                     include_params ? "empty <params>" : "no <params>");
-
-       params = soup_value_array_new ();
-       request = soup_xmlrpc_build_method_call ("ping", params->values,
-                                                params->n_values);
-       g_value_array_free (params);
-       if (!request)
-               return;
-
-       if (!include_params) {
-               char *params, *end;
-
-               params = strstr (request, "<params/>");
-               if (!params) {
-                       soup_test_assert (FALSE, "ERROR: XML did not contain <params/>!");
-                       return;
-               }
-               end = params + strlen ("<params/>");
-               memmove (params, end, strlen (end) + 1);
+       char *body;
+       char *method_name;
+       GVariant *variant;
+       GError *error = NULL;
+
+       body = g_strconcat (BODY_PREFIX, params, BODY_SUFFIX, NULL);
+       method_name = soup_xmlrpc_parse_request_full (body, strlen (body),
+                                                     signature,
+                                                     &variant,
+                                                     &error);
+       g_assert_no_error (error);
+       g_assert_cmpstr (method_name, ==, "MyMethod");
+
+       if (!g_variant_equal (variant, expected_variant)) {
+               char *str1, *str2;
+
+               str1 = g_variant_print (expected_variant, TRUE);
+               str2 = g_variant_print (variant, TRUE);
+               g_error ("Failed to deserialize '%s':\n"
+                        "  expected: %s\n"
+                        "  got:      %s\n",
+                        params, str1, str2);
+               g_free (str1);
+               g_free (str2);
        }
 
-       ret = send_xmlrpc (request, &retval);
-       g_free (request);
-
-       if (!ret || !check_xmlrpc (&retval, G_TYPE_STRING, &out))
-               return;
-
-       g_assert_cmpstr (out, ==, "pong");
-
-       g_free (out);
+       g_variant_unref (variant);
+       g_free (method_name);
+       g_free (body);
 }
 
 static void
-do_bad_xmlrpc (const char *body)
+verify_deserialization_fail (const char *signature,
+                            const char *params)
 {
-       SoupMessage *msg;
-       GError *err = NULL;
-       GValue retval;
-
-       msg = soup_message_new ("POST", uri);
-       soup_message_set_request (msg, "text/xml", SOUP_MEMORY_COPY,
-                                 body, strlen (body));
-       soup_session_send_message (session, msg);
-
-       soup_test_assert_message_status (msg, SOUP_STATUS_OK);
-
-       if (!soup_xmlrpc_parse_method_response (msg->response_body->data,
-                                               msg->response_body->length,
-                                               &retval, &err)) {
-               if (err) {
-                       debug_printf (1, "FAULT: %d %s (OK!)\n",
-                                     err->code, err->message);
-                       g_error_free (err);
-                       g_object_unref (msg);
-                       return;
-               } else
-                       soup_test_assert (FALSE, "ERROR: could not parse response\n");
-       } else
-               soup_test_assert (FALSE, "Unexpectedly got successful response!\n");
+       char *body;
+       char *method_name;
+       GVariant *variant;
+       GError *error = NULL;
+
+       body = g_strconcat (BODY_PREFIX, params, BODY_SUFFIX, NULL);
+       method_name = soup_xmlrpc_parse_request_full (body, strlen (body),
+                                                     signature,
+                                                     &variant,
+                                                     &error);
+       g_assert_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS);
+       g_assert (method_name == NULL);
 
-       g_object_unref (msg);
+       g_free (body);
 }
 
 static void
-test_fault_malformed (void)
+test_deserializer (void)
 {
-       SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER;
-
-       do_bad_xmlrpc ("<methodCall/>");
+       char *tmp;
+
+       verify_deserialization (g_variant_new_parsed ("@av []"),
+               NULL,
+               "<params/>");
+       verify_deserialization (g_variant_new_parsed ("()"),
+               "()",
+               "<params/>");
+       verify_deserialization (g_variant_new_parsed ("(@y 1,@n 2)"),
+               "(yn)",
+               "<params>"
+               "<param><value><int>1</int></value></param>"
+               "<param><value><int>2</int></value></param>"
+               "</params>");
+       verify_deserialization (g_variant_new_parsed ("[<[{'one', <1>},{'two', <2>}]>]"),
+               NULL,
+               "<params><param><value><struct>"
+               "<member><name>one</name><value><int>1</int></value></member>"
+               "<member><name>two</name><value><int>2</int></value></member>"
+               "</struct></value></param></params>");
+       verify_deserialization (g_variant_new_parsed ("([{'one', 1},{'two', 2}],)"),
+               "(a{si})",
+               "<params><param><value><struct>"
+               "<member><name>one</name><value><int>1</int></value></member>"
+               "<member><name>two</name><value><int>2</int></value></member>"
+               "</struct></value></param></params>");
+       verify_deserialization (g_variant_new_parsed ("[<int64 1434161309>]"),
+               NULL,
+               "<params>"
+               "<param><value><dateTime.iso8601>2015-06-12T22:08:29-04:00</dateTime.iso8601></value></param>"
+               "</params>");
+       verify_deserialization (g_variant_new_parsed ("[<b'bytestring'>]"),
+               NULL,
+               "<params>"
+               "<param><value><base64>Ynl0ZXN0cmluZwA=</base64></value></param>"
+               "</params>");
+       verify_deserialization (g_variant_new_parsed ("(@o '/path',)"),
+               "(o)",
+               "<params>"
+               "<param><value><string>/path</string></value></param>"
+               "</params>");
+       verify_deserialization (g_variant_new_parsed ("[<1>]"),
+               "av",
+               "<params><param><value><int>1</int></value></param></params>");
+       verify_deserialization (g_variant_new_parsed ("[<%s>]", "<>&"),
+               NULL,
+               "<params>"
+               "<param><value><string>&lt;&gt;&amp;</string></value></param>"
+               "</params>");
+       verify_deserialization (g_variant_new_parsed ("(@y 255,)"),
+               "(y)",
+               "<params>"
+               "<param><value><int>255</int></value></param>"
+               "</params>");
+
+       tmp = g_strdup_printf ("<params>"
+               "<param><value><int>%"G_GUINT64_FORMAT"</int></value></param>"
+               "</params>", G_MAXUINT64);
+       verify_deserialization (g_variant_new ("(t)", G_MAXUINT64),
+               "(t)", tmp);
+       g_free (tmp);
+
+       verify_deserialization_fail ("(o)",
+               "<params>"
+               "<param><value><string>not/a/path</string></value></param>"
+               "</params>");
+       verify_deserialization_fail (NULL,
+               "<params>"
+               "<param><value><boolean>2</boolean></value></param>"
+               "</params>");
+       verify_deserialization_fail ("(y)",
+               "<params>"
+               "<param><value><int>256</int></value></param>"
+               "</params>");
 }
 
 static void
-test_fault_method (void)
+test_fault (void)
 {
-       SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER;
+       char *body;
+       GVariant *reply;
+       GError *error = NULL;
 
-       do_bad_xmlrpc 
("<methodCall><methodName>no_such_method</methodName><params><param><value><int>1</int></value></param></params></methodCall>");
-}
+       body = soup_xmlrpc_build_fault (1, "error: %s", "failed");
+       reply = soup_xmlrpc_parse_response (body, strlen (body), NULL, &error);
+       g_assert_error (error, SOUP_XMLRPC_FAULT, 1);
+       g_assert_cmpstr (error->message, ==, "error: failed");
+       g_assert (reply == NULL);
 
-static void
-test_fault_args (void)
-{
-       SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER;
-
-       do_bad_xmlrpc 
("<methodCall><methodName>sum</methodName><params><param><value><int>1</int></value></param></params></methodCall>");
+       g_free (body);
+       g_clear_error (&error);
 }
 
-static GOptionEntry xmlrpc_entries[] = {
-        { "uri", 'U', 0, G_OPTION_ARG_STRING, &uri,
-          "Alternate URI for server", NULL },
-        { "server-test", 'S', 0, G_OPTION_ARG_NONE, &server_test,
-          "If this is being run from xmlrpc-server-test", NULL },
-        { NULL }
-};
-
 int
 main (int argc, char **argv)
 {
-       int ret;
-
-       test_init (argc, argv, xmlrpc_entries);
-
-       if (!uri && !server_test) {
-               apache_init ();
-               uri = default_uri;
-       }
-
-       session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
-
-       g_test_add_func ("/xmlrpc/sum", test_sum);
-       g_test_add_func ("/xmlrpc/countBools", test_countBools);
-       g_test_add_func ("/xmlrpc/md5sum", test_md5sum);
-       g_test_add_func ("/xmlrpc/dateChange", test_dateChange);
-       g_test_add_func ("/xmlrpc/echo", test_echo);
-       g_test_add_data_func ("/xmlrpc/ping/empty-params", GINT_TO_POINTER (TRUE), test_ping);
-       g_test_add_data_func ("/xmlrpc/ping/no-params", GINT_TO_POINTER (FALSE), test_ping);
-       g_test_add_func ("/xmlrpc/fault/malformed", test_fault_malformed);
-       g_test_add_func ("/xmlrpc/fault/method", test_fault_method);
-       g_test_add_func ("/xmlrpc/fault/args", test_fault_args);
-
-       ret = g_test_run ();
+       g_test_init (&argc, &argv, NULL);
 
-       soup_test_session_abort_unref (session);
+       g_test_add_func ("/xmlrpc/variant/serializer", test_serializer);
+       g_test_add_func ("/xmlrpc/variant/deserializer", test_deserializer);
+       g_test_add_func ("/xmlrpc/variant/fault", test_fault);
 
-       test_cleanup ();
-       return ret;
+       return g_test_run ();
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]