[libsoup/wip/xclaesse/xmlrpc: 1/10] xmlrpc: Add GVariant (de)serializer



commit 0b533411a3e581fca1cab0041d09d9f94c71ed5c
Author: Xavier Claessens <xavier claessens collabora com>
Date:   Fri Jun 12 13:28:34 2015 -0400

    xmlrpc: Add GVariant (de)serializer
    
    https://bugzilla.gnome.org/show_bug.cgi?id=746495

 docs/reference/libsoup-2.4-sections.txt |   16 +-
 libsoup/Makefile.am                     |    6 +-
 libsoup/libsoup-2.4.sym                 |    9 +
 libsoup/soup-xmlrpc-variant.c           | 1245 +++++++++++++++++++++++++++++++
 libsoup/soup-xmlrpc-variant.h           |   52 ++
 libsoup/soup.h                          |    1 +
 tests/Makefile.am                       |    3 +-
 tests/xmlrpc-variant-test.c             |  239 ++++++
 8 files changed, 1563 insertions(+), 8 deletions(-)
---
diff --git a/docs/reference/libsoup-2.4-sections.txt b/docs/reference/libsoup-2.4-sections.txt
index 306fa6f..00163b9 100644
--- a/docs/reference/libsoup-2.4-sections.txt
+++ b/docs/reference/libsoup-2.4-sections.txt
@@ -830,6 +830,17 @@ soup_form_encode_urlencoded_list
 <FILE>soup-xmlrpc</FILE>
 <TITLE>XMLRPC Support</TITLE>
 <SUBSECTION>
+soup_xmlrpc_build_request
+soup_xmlrpc_message_new
+<SUBSECTION>
+soup_xmlrpc_build_response
+soup_xmlrpc_message_set_response
+soup_xmlrpc_build_fault
+soup_xmlrpc_set_fault
+<SUBSECTION>
+SOUP_XMLRPC_FAULT
+SoupXMLRPCFault
+<SUBSECTION>
 soup_xmlrpc_build_method_call
 soup_xmlrpc_request_new
 soup_xmlrpc_parse_method_response
@@ -838,12 +849,7 @@ soup_xmlrpc_extract_method_response
 soup_xmlrpc_parse_method_call
 soup_xmlrpc_extract_method_call
 soup_xmlrpc_build_method_response
-soup_xmlrpc_build_fault
 soup_xmlrpc_set_response
-soup_xmlrpc_set_fault
-<SUBSECTION>
-SOUP_XMLRPC_FAULT
-SoupXMLRPCFault
 <SUBSECTION Private>
 soup_xmlrpc_error_quark
 SOUP_XMLRPC_ERROR
diff --git a/libsoup/Makefile.am b/libsoup/Makefile.am
index 998bd41..f864769 100644
--- a/libsoup/Makefile.am
+++ b/libsoup/Makefile.am
@@ -70,7 +70,8 @@ soup_headers =                        \
        soup-value-utils.h      \
        soup-websocket.h        \
        soup-websocket-connection.h     \
-       soup-xmlrpc.h
+       soup-xmlrpc.h           \
+       soup-xmlrpc-variant.h
 
 libsoupinclude_HEADERS =       \
        $(soup_headers)         \
@@ -192,7 +193,8 @@ libsoup_2_4_la_SOURCES =            \
        soup-version.c                  \
        soup-websocket.c                \
        soup-websocket-connection.c     \
-       soup-xmlrpc.c
+       soup-xmlrpc.c                   \
+       soup-xmlrpc-variant.c
 
 # TLD rules
 EXTRA_DIST += tld-parser.py
diff --git a/libsoup/libsoup-2.4.sym b/libsoup/libsoup-2.4.sym
index e881b7f..5fe2e08 100644
--- a/libsoup/libsoup-2.4.sym
+++ b/libsoup/libsoup-2.4.sym
@@ -543,14 +543,23 @@ soup_websocket_state_get_type
 soup_xmlrpc_build_fault
 soup_xmlrpc_build_method_call
 soup_xmlrpc_build_method_response
+soup_xmlrpc_build_request
+soup_xmlrpc_build_response
 soup_xmlrpc_error_get_type
 soup_xmlrpc_error_quark
 soup_xmlrpc_extract_method_call
 soup_xmlrpc_extract_method_response
 soup_xmlrpc_fault_get_type
 soup_xmlrpc_fault_quark
+soup_xmlrpc_message_new
+soup_xmlrpc_message_set_response
+soup_xmlrpc_params_free
+soup_xmlrpc_params_parse
 soup_xmlrpc_parse_method_call
 soup_xmlrpc_parse_method_response
+soup_xmlrpc_parse_request
+soup_xmlrpc_parse_request_full
+soup_xmlrpc_parse_response
 soup_xmlrpc_request_new
 soup_xmlrpc_set_fault
 soup_xmlrpc_set_response
diff --git a/libsoup/soup-xmlrpc-variant.c b/libsoup/soup-xmlrpc-variant.c
new file mode 100644
index 0000000..3aa6b5e
--- /dev/null
+++ b/libsoup/soup-xmlrpc-variant.c
@@ -0,0 +1,1245 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-xmlrpc.c: XML-RPC parser/generator
+ *
+ * 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
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <errno.h>
+#include <libxml/tree.h>
+#include "soup-xmlrpc-variant.h"
+#include "soup.h"
+
+static gboolean insert_value (xmlNode  *parent, GVariant *value, GError **error);
+
+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);
+       }
+
+       return TRUE;
+}
+
+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_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)
+{
+       xmlNode *xvalue;
+       const gchar *type_str = NULL;
+       gchar buf[128];
+
+       xvalue = xmlNewChild (parent, NULL, (const xmlChar *)"value", NULL);
+
+       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_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_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:
+               if (!insert_array (xvalue, value, error))
+                       return FALSE;
+               break;
+       case G_VARIANT_CLASS_DICT_ENTRY: {
+               xmlNode *node;
+
+               node = xmlNewChild (xvalue, NULL,
+                                   (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_UINT32:
+       case G_VARIANT_CLASS_INT64:
+       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_request:
+ * @method_name: the name of the XML-RPC method
+ * @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_message_new() is
+ * built on.
+ *
+ * @params is a #GVariant tuple representing the method parameters.
+ *
+ * Limitations that will cause method to return %FALSE and set @error:
+ *  - maybes, uint32, int64 and uint64 cannot be serialized.
+ *  - Dictionaries must have string keys.
+ *
+ * Special cases:
+ *  - "a{s*}" are serialized as &lt;struct&gt;
+ *  - "ay" are serialized as &lt;base64&gt;
+ *  - tuples are serialized as &lt;array&gt;
+ *
+ * It is not currently possible to build &lt;dateTime.iso8601&gt; values since
+ * #GVariant does not have date/time type. If sending a date is required then
+ * soup_xmlrpc_build_method_call() should be used instead.
+ *
+ * If @params is floating, it is consumed.
+ *
+ * Return value: the text of the methodCall, or %NULL on error.
+ **/
+gchar *
+soup_xmlrpc_build_request (const gchar *method_name,
+                          GVariant    *params,
+                          GError     **error)
+{
+       xmlDoc *doc;
+       xmlNode *node, *param;
+       xmlChar *xmlbody;
+       GVariantIter iter;
+       GVariant *child;
+       gint len;
+       gchar *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;
+       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);
+       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, 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);
+       g_variant_unref (params);
+
+fail:
+       return body;
+}
+
+/**
+ * soup_xmlrpc_message_new:
+ * @uri: URI of the XML-RPC service
+ * @method_name: the name of the XML-RPC method to invoke at @uri
+ * @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.
+ *
+ * See soup_xmlrpc_build_request() for serialization details.
+ *
+ * If @params is floating, it is consumed.
+ *
+ * Returns: (transfer full): a #SoupMessage encoding the
+ *  indicated XML-RPC request, or %NULL on error.
+ **/
+SoupMessage *
+soup_xmlrpc_message_new (const gchar *uri,
+                        const gchar *method_name,
+                        GVariant    *params,
+                        GError     **error)
+{
+       SoupMessage *msg;
+       gchar *body;
+
+       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_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(). This
+ * is the low-level method that soup_xmlrpc_message_set_response() is built on.
+ *
+ * See soup_xmlrpc_build_request() for serialization details.
+ *
+ * If @value is floating, it is consumed.
+ *
+ * Returns: the text of the methodResponse, or %NULL on error.
+ **/
+gchar *
+soup_xmlrpc_build_response (GVariant *value, GError **error)
+{
+       xmlDoc *doc;
+       xmlNode *node;
+       xmlChar *xmlbody;
+       gchar *body;
+       gint len;
+
+       g_variant_ref_sink (value);
+
+       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, error)) {
+               xmlFreeDoc (doc);
+               g_variant_unref (value);
+               return NULL;
+       }
+
+       xmlDocDumpMemory (doc, &xmlbody, &len);
+       body = g_strndup ((gchar *)xmlbody, len);
+       xmlFree (xmlbody);
+
+       xmlFreeDoc (doc);
+       g_variant_unref (value);
+
+       return body;
+}
+
+/**
+ * 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.
+ **/
+gboolean
+soup_xmlrpc_message_set_response (SoupMessage *msg, GVariant *value, GError **error)
+{
+       gchar *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 gchar **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 gchar *
+signature_get_next_complete_type (const gchar **signature)
+{
+       GVariantClass class;
+       const gchar *initial_signature;
+       gchar *result;
+
+       /* here it is assumed that 'signature' is a valid type string */
+
+       initial_signature = *signature;
+       class = (*signature)[0];
+
+       if (class == G_VARIANT_CLASS_TUPLE || class == G_VARIANT_CLASS_DICT_ENTRY) {
+               gchar stack[256] = {0};
+               guint stack_len = 0;
+
+               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++;
+                       }
+
+                       (*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) {
+               gchar *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;
+}
+
+static GVariant *
+parse_array (xmlNode *node, const gchar **signature, GError **error)
+{
+       GVariant *variant = NULL;
+       gchar *child_signature = NULL;
+       gchar *array_signature = NULL;
+       const gchar *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 ();
+       }
+
+       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;
+}
+
+static void
+parse_dict_entry_signature (const gchar **signature,
+                           gchar       **entry_signature,
+                           gchar       **key_signature,
+                           gchar       **value_signature)
+{
+       const gchar *tmp_sig;
+
+       if (signature)
+               *entry_signature = signature_get_next_complete_type (signature);
+       else
+               *entry_signature = g_strdup ("{sv}");
+
+       tmp_sig = (*entry_signature) + 1;
+       *key_signature = signature_get_next_complete_type (&tmp_sig);
+       *value_signature = signature_get_next_complete_type (&tmp_sig);
+}
+
+static GVariant *
+parse_dictionary (xmlNode *node, const gchar **signature, GError **error)
+{
+       GVariant *variant = NULL;
+       gchar *dict_signature;
+       gchar *entry_signature;
+       gchar *key_signature;
+       gchar *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;
+       }
+
+       for (member = find_real_node (node->children);
+            member;
+            member = find_real_node (member->next)) {
+               xmlNode *child, *mname, *mxval;
+               const gchar *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;
+               }
+
+               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;
+}
+
+static GVariant *
+parse_number (xmlNode *typenode, GVariantClass class, GError **error)
+{
+       xmlChar *content;
+       const gchar *str;
+       gchar *endptr;
+       gint64 num = 0;
+       guint64 unum = 0;
+       GVariant *variant = NULL;
+
+       content = xmlNodeGetContent (typenode);
+       str = (const gchar *) content;
+
+       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 gchar *)typenode->name);
+               goto fail;
+       }
+
+fail:
+       xmlFree (content);
+
+       return variant;
+}
+
+static GVariant *
+parse_double (xmlNode *typenode, GError **error)
+{
+       GVariant *variant = NULL;
+       xmlChar *content;
+       const gchar *str;
+       gchar *endptr;
+       gdouble d;
+
+       content = xmlNodeGetContent (typenode);
+       str = (const gchar *) 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);
+
+fail:
+       xmlFree (content);
+
+       return variant;
+}
+
+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 gchar **signature, GError **error)
+{
+       xmlNode *typenode;
+       const gchar *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, "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);
+               if (class == G_VARIANT_CLASS_VARIANT || class == G_VARIANT_CLASS_STRING)
+                       variant = g_variant_new_string ((const gchar *)content);
+               else if (class == G_VARIANT_CLASS_OBJECT_PATH &&
+                        g_variant_is_object_path ((const gchar *)content))
+                       variant = g_variant_new_object_path ((const gchar *)content);
+               else if (class == G_VARIANT_CLASS_SIGNATURE &&
+                        g_variant_is_signature ((const gchar *)content))
+                       variant = g_variant_new_signature ((const gchar *)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 ((gchar *)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;
+       }
+
+       if (variant && signature) {
+               if (class == G_VARIANT_CLASS_VARIANT)
+                       variant = g_variant_new_variant (variant);
+               (*signature)++;
+       }
+
+fail:
+       if (content)
+               xmlFree (content);
+
+       return variant;
+}
+
+/**
+ * SoupXMLRPCParams:
+ *
+ * Opaque structure containing an XML-RPC value. Can be parsed using
+ * soup_xmlrpc_params_parse() and freed with soup_xmlrpc_params_free().
+ */
+struct _SoupXMLRPCParams
+{
+  xmlNode *node;
+};
+
+/**
+ * soup_xmlrpc_params_free:
+ * @self: a SoupXMLRPCParams
+ *
+ * Free a #SoupXMLRPCParams returned by soup_xmlrpc_parse_request().
+ */
+void
+soup_xmlrpc_params_free (SoupXMLRPCParams *self)
+{
+       g_return_if_fail (self != NULL);
+
+       if (self->node)
+               xmlFreeDoc (self->node->doc);
+       g_slice_free (SoupXMLRPCParams, self);
+}
+
+static SoupXMLRPCParams *
+soup_xmlrpc_params_new (xmlNode *node)
+{
+       SoupXMLRPCParams *self;
+
+       self = g_slice_new (SoupXMLRPCParams);
+       self->node = node;
+
+       return self;
+}
+
+/**
+ * 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
+ */
+GVariant *
+soup_xmlrpc_params_parse (SoupXMLRPCParams *self,
+                         const gchar      *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
+ * @params: (out): on success, a new #SoupXMLRPCParams
+ * @error: a #GError, or %NULL
+ *
+ * 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.
+ *
+ * Returns: (transfer full): method's name, or %NULL on error.
+ **/
+gchar *
+soup_xmlrpc_parse_request (const gchar *method_call,
+                          gint length,
+                          SoupXMLRPCParams **params,
+                          GError **error)
+{
+       xmlDoc *doc = NULL;
+       xmlNode *node;
+       xmlChar *xmlMethodName = NULL;
+       gchar *method_name = NULL;
+
+       doc = xmlParseMemory (method_call,
+                                 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 gchar *)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) {
+               g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                            "<methodName> node expected");
+               goto fail;
+       }
+       xmlMethodName = xmlNodeGetContent (node);
+
+       if (params) {
+               node = find_real_node (node->next);
+               if (!node || strcmp ((const gchar *)node->name, "params") != 0) {
+                       g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                                    "<params> node expected");
+                       goto fail;
+               }
+               *params = soup_xmlrpc_params_new (node);
+               doc = NULL;
+       }
+
+       method_name = g_strdup ((gchar *)xmlMethodName);
+
+fail:
+       if (doc)
+               xmlFreeDoc (doc);
+       if (xmlMethodName)
+               xmlFree (xmlMethodName);
+
+       return method_name;
+}
+
+/**
+ * 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
+ * @signature: (allow-none): A valid #GVariant type string, or %NULL
+ * @parameters: (out): on success, a new #GVariant
+ * @error: a #GError, or %NULL
+ *
+ * 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.
+ *
+ * Limitations that will cause method to return %NULL and set @error:
+ *  - @signature must not have maybes.
+ *  - Dictionaries must have string keys.
+ *
+ * Special cases:
+ *  - 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.
+ *
+ * Returns: (transfer full): method's name, or %NULL on error.
+ **/
+gchar *
+soup_xmlrpc_parse_request_full (const gchar *method_call,
+                               gint         length,
+                               const gchar *signature,
+                               GVariant   **parameters,
+                               GError     **error)
+{
+       gchar *method_name;
+       SoupXMLRPCParams *params;
+
+       method_name = soup_xmlrpc_parse_request (method_call,
+                                                length,
+                                                parameters ? &params : NULL,
+                                                error);
+       if (!method_name)
+               return NULL;
+
+       if (parameters) {
+               *parameters = soup_xmlrpc_params_parse (params, signature, error);
+               if (*parameters == NULL) {
+                       g_free (method_name);
+                       return NULL;
+               }
+       }
+
+       return method_name;
+}
+
+/**
+ * soup_xmlrpc_parse_response:
+ * @method_response: the XML-RPC methodResponse string
+ * @length: the length of @method_response, or -1 if it is NUL-terminated
+ * @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
+ **/
+GVariant *
+soup_xmlrpc_parse_response (const gchar *method_response,
+                           gint length,
+                           const gchar *signature,
+                           GError **error)
+{
+       xmlDoc *doc = NULL;
+       xmlNode *node;
+       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) {
+               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) {
+               g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                            "Missing 'methodResponse' node");
+               goto fail;
+       }
+
+       node = find_real_node (node->children);
+       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")) {
+               int fault_code;
+               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) {
+                       g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                                    "'fault' has no 'value' child");
+                       goto fail;
+               }
+
+               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_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) {
+                       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) {
+                       g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+                                    "'param' has no 'value' child");
+                       goto fail;
+               }
+               value = parse_value (node, signature ? &signature : NULL, error);
+       }
+
+fail:
+       if (doc)
+               xmlFreeDoc (doc);
+       return value ? g_variant_ref_sink (value) : NULL;
+}
diff --git a/libsoup/soup-xmlrpc-variant.h b/libsoup/soup-xmlrpc-variant.h
new file mode 100644
index 0000000..38db959
--- /dev/null
+++ b/libsoup/soup-xmlrpc-variant.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright 2015 - Collabora Ltd.
+ */
+
+#ifndef SOUP_XMLRPC_VARIANT_H
+#define SOUP_XMLRPC_VARIANT_H 1
+
+#include <libsoup/soup-types.h>
+#include <libsoup/soup-xmlrpc.h>
+
+G_BEGIN_DECLS
+
+/* XML-RPC client */
+gchar       *soup_xmlrpc_build_request  (const gchar *method_name,
+                                        GVariant    *params,
+                                        GError     **error);
+SoupMessage *soup_xmlrpc_message_new    (const gchar *uri,
+                                        const gchar *method_name,
+                                        GVariant    *params,
+                                        GError     **error);
+GVariant    *soup_xmlrpc_parse_response (const gchar *method_response,
+                                        gint         length,
+                                        const gchar *signature,
+                                        GError     **error);
+
+/* XML-RPC server */
+typedef struct _SoupXMLRPCParams SoupXMLRPCParams;
+void         soup_xmlrpc_params_free          (SoupXMLRPCParams  *self);
+GVariant    *soup_xmlrpc_params_parse         (SoupXMLRPCParams  *self,
+                                              const gchar       *signature,
+                                              GError           **error);
+gchar       *soup_xmlrpc_parse_request        (const gchar       *method_call,
+                                              gint               length,
+                                              SoupXMLRPCParams **params,
+                                              GError           **error);
+gchar       *soup_xmlrpc_parse_request_full   (const gchar       *method_call,
+                                              gint               length,
+                                              const gchar       *signature,
+                                              GVariant         **parameters,
+                                              GError           **error);
+gchar       *soup_xmlrpc_build_response       (GVariant          *value,
+                                              GError           **error);
+gboolean     soup_xmlrpc_message_set_response (SoupMessage       *msg,
+                                              GVariant          *value,
+                                              GError           **error);
+
+
+
+G_END_DECLS
+
+#endif /* SOUP_XMLRPC_VARIANT_H */
diff --git a/libsoup/soup.h b/libsoup/soup.h
index 7106cc5..8a6bef8 100644
--- a/libsoup/soup.h
+++ b/libsoup/soup.h
@@ -53,6 +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>
 
 #ifdef __cplusplus
 }
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 662ab79..83f9508 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -43,7 +43,8 @@ test_programs =                       \
        uri-parsing             \
        websocket-test          \
        xmlrpc-server-test      \
-       xmlrpc-test
+       xmlrpc-test             \
+       xmlrpc-variant-test
 
 test_extra_programs =          \
        ntlm-test-helper        \
diff --git a/tests/xmlrpc-variant-test.c b/tests/xmlrpc-variant-test.c
new file mode 100644
index 0000000..d5de84a
--- /dev/null
+++ b/tests/xmlrpc-variant-test.c
@@ -0,0 +1,239 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright 2015, Collabora ltd.
+ */
+
+#include "test-utils.h"
+
+#define BODY_PREFIX \
+       "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" \
+       "<methodCall><methodName>MyMethod</methodName>"
+#define BODY_SUFFIX \
+       "</methodCall>\n"
+
+static void
+verify_serialization (GVariant    *value,
+                     const gchar *expected_params)
+{
+       gchar *debug;
+       gchar *body;
+       gchar *params;
+       GError *error = NULL;
+
+       debug = g_variant_print (value, TRUE);
+
+       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));
+
+       params = g_strndup (body + strlen (BODY_PREFIX),
+                           strlen (body) - strlen (BODY_PREFIX)
+                                         - strlen (BODY_SUFFIX));
+
+       if (!g_str_equal (params, expected_params))
+               g_error ("Failed to serialize '%s':\n"
+                        "  expected: %s\n"
+                        "  got:      %s\n",
+                        debug, expected_params, params);
+
+       g_free (params);
+       g_free (body);
+       g_free (debug);
+}
+
+static void
+verify_serialization_fail (GVariant *value)
+{
+       gchar *body;
+       GError *error = NULL;
+
+       body = soup_xmlrpc_build_request ("MyMethod", value, &error);
+       g_assert (body == NULL);
+       g_assert (error != NULL);
+}
+
+static void
+test_serializer (void)
+{
+       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>2015-06-13T02:08:29Z</dateTime.iso8601></value></param>"
+               "</params>");
+       verify_serialization (g_variant_new ("(s)", "<>&"),
+               "<params>"
+               "<param><value><string>&lt;&gt;&amp;</string></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 ("(u)", 0));
+       verify_serialization_fail (g_variant_new ("(t)", G_MAXUINT64));
+}
+
+static void
+verify_deserialization (GVariant *expected_variant,
+                       const gchar *signature,
+                       const gchar *params)
+{
+       gchar *body;
+       gchar *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)) {
+               gchar *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);
+       }
+
+       g_variant_unref (variant);
+       g_free (method_name);
+       g_free (body);
+}
+
+static void
+verify_deserialization_fail (const gchar *signature,
+                            const gchar *params)
+{
+       gchar *body;
+       gchar *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_free (body);
+}
+
+static void
+test_deserializer (void)
+{
+       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>");
+
+       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>");
+}
+
+int
+main (int argc, char **argv)
+{
+       g_test_init (&argc, &argv, NULL);
+
+       g_test_add_func ("/xmlrpc/variant/serializer", test_serializer);
+       g_test_add_func ("/xmlrpc/variant/deserializer", test_deserializer);
+
+       return g_test_run ();
+}


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