summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXavier Claessens <xavier.claessens@collabora.com>2015-07-27 15:52:17 -0400
committerXavier Claessens <xavier.claessens@collabora.com>2015-08-13 11:28:39 -0400
commit2dd764906f7d7403b6c47c3e339177085f540c1b (patch)
treee9e081f7a057475f45234f29ad2e9e15c62658da
parentb9c0435d72cfdb0ca615181a8fad6ebe5fa8363e (diff)
downloadlibsoup-2dd764906f7d7403b6c47c3e339177085f540c1b.tar.gz
xmlrpc: Add GVariant (de)serializer
https://bugzilla.gnome.org/show_bug.cgi?id=746495
-rw-r--r--docs/reference/libsoup-2.4-sections.txt27
-rw-r--r--libsoup/Makefile.am2
-rw-r--r--libsoup/libsoup-2.4.sym11
-rw-r--r--libsoup/soup-xmlrpc-old.c138
-rw-r--r--libsoup/soup-xmlrpc-old.h29
-rw-r--r--libsoup/soup-xmlrpc.c1479
-rw-r--r--libsoup/soup-xmlrpc.h94
-rw-r--r--libsoup/soup.h1
-rw-r--r--tests/Makefile.am4
-rw-r--r--tests/xmlrpc-old-server-test.c2
-rw-r--r--tests/xmlrpc-server-test.c374
-rw-r--r--tests/xmlrpc-test.c749
12 files changed, 2738 insertions, 172 deletions
diff --git a/docs/reference/libsoup-2.4-sections.txt b/docs/reference/libsoup-2.4-sections.txt
index 21757a4d..490c36c3 100644
--- a/docs/reference/libsoup-2.4-sections.txt
+++ b/docs/reference/libsoup-2.4-sections.txt
@@ -830,24 +830,37 @@ soup_form_encode_urlencoded_list
<FILE>soup-xmlrpc</FILE>
<TITLE>XMLRPC Support</TITLE>
<SUBSECTION>
+SOUP_XMLRPC_ERROR
+SoupXMLRPCError
+SOUP_XMLRPC_FAULT
+SoupXMLRPCFault
+<SUBSECTION>
+soup_xmlrpc_build_request
+soup_xmlrpc_message_new
+soup_xmlrpc_parse_response
+soup_xmlrpc_variant_new_datetime
+soup_xmlrpc_variant_get_datetime
+<SUBSECTION>
+SoupXMLRPCParams
+soup_xmlrpc_params_free
+soup_xmlrpc_params_parse
+soup_xmlrpc_parse_request
+soup_xmlrpc_build_response
+soup_xmlrpc_build_fault
+soup_xmlrpc_message_set_response
+soup_xmlrpc_message_set_fault
+<SUBSECTION>
soup_xmlrpc_build_method_call
soup_xmlrpc_request_new
soup_xmlrpc_parse_method_response
soup_xmlrpc_extract_method_response
-<SUBSECTION>
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
-SoupXMLRPCError
soup_xmlrpc_fault_quark
</SECTION>
diff --git a/libsoup/Makefile.am b/libsoup/Makefile.am
index 5c51e251..ec7fe82f 100644
--- a/libsoup/Makefile.am
+++ b/libsoup/Makefile.am
@@ -72,6 +72,7 @@ soup_headers = \
soup-value-utils.h \
soup-websocket.h \
soup-websocket-connection.h \
+ soup-xmlrpc.h \
soup-xmlrpc-old.h
libsoupinclude_HEADERS = \
@@ -194,6 +195,7 @@ libsoup_2_4_la_SOURCES = \
soup-version.c \
soup-websocket.c \
soup-websocket-connection.c \
+ soup-xmlrpc.c \
soup-xmlrpc-old.c
# TLD rules
diff --git a/libsoup/libsoup-2.4.sym b/libsoup/libsoup-2.4.sym
index e881b7f9..e6ff89e8 100644
--- a/libsoup/libsoup-2.4.sym
+++ b/libsoup/libsoup-2.4.sym
@@ -543,14 +543,25 @@ 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_fault
+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_response
soup_xmlrpc_request_new
soup_xmlrpc_set_fault
soup_xmlrpc_set_response
+soup_xmlrpc_variant_get_datetime
+soup_xmlrpc_variant_new_datetime
diff --git a/libsoup/soup-xmlrpc-old.c b/libsoup/soup-xmlrpc-old.c
index df80985d..4796134a 100644
--- a/libsoup/soup-xmlrpc-old.c
+++ b/libsoup/soup-xmlrpc-old.c
@@ -291,84 +291,6 @@ soup_xmlrpc_build_method_response (GValue *value)
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
@@ -397,6 +319,10 @@ soup_xmlrpc_set_response (SoupMessage *msg, GType type, ...)
body, strlen (body));
}
+char *soup_xmlrpc_build_faultv (int fault_code,
+ const char *fault_format,
+ va_list args) G_GNUC_PRINTF (2, 0);
+
/**
* soup_xmlrpc_set_fault:
* @msg: an XML-RPC request
@@ -807,62 +733,6 @@ soup_xmlrpc_extract_method_response (const char *method_response, int length,
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)
{
diff --git a/libsoup/soup-xmlrpc-old.h b/libsoup/soup-xmlrpc-old.h
index f96deec7..23db5029 100644
--- a/libsoup/soup-xmlrpc-old.h
+++ b/libsoup/soup-xmlrpc-old.h
@@ -37,9 +37,6 @@ gboolean soup_xmlrpc_extract_method_call (const char *method_call,
char **method_name,
...);
char *soup_xmlrpc_build_method_response (GValue *value);
-char *soup_xmlrpc_build_fault (int fault_code,
- const char *fault_format,
- ...) G_GNUC_PRINTF (2, 3);
void soup_xmlrpc_set_response (SoupMessage *msg,
GType type,
...);
@@ -48,32 +45,6 @@ void soup_xmlrpc_set_fault (SoupMessage *msg,
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_OLD_H */
diff --git a/libsoup/soup-xmlrpc.c b/libsoup/soup-xmlrpc.c
new file mode 100644
index 00000000..b1673d94
--- /dev/null
+++ b/libsoup/soup-xmlrpc.c
@@ -0,0 +1,1479 @@
+/* -*- 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.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 char *type_str = NULL;
+ char buf[128];
+
+ xvalue = xmlNewChild (parent, NULL, (const xmlChar *)"value", NULL);
+
+ switch (g_variant_classify (value)) {
+ case G_VARIANT_CLASS_BOOLEAN:
+ g_snprintf (buf, sizeof (buf), "%d", g_variant_get_boolean (value));
+ type_str = "boolean";
+ break;
+ case G_VARIANT_CLASS_BYTE:
+ g_snprintf (buf, sizeof (buf), "%u", g_variant_get_byte (value));
+ type_str = "int";
+ break;
+ case G_VARIANT_CLASS_INT16:
+ g_snprintf (buf, sizeof (buf), "%d", g_variant_get_int16 (value));
+ type_str = "int";
+ break;
+ case G_VARIANT_CLASS_UINT16:
+ g_snprintf (buf, sizeof (buf), "%u", g_variant_get_uint16 (value));
+ type_str = "int";
+ break;
+ case G_VARIANT_CLASS_INT32:
+ g_snprintf (buf, sizeof (buf), "%d", g_variant_get_int32 (value));
+ type_str = "int";
+ break;
+ case G_VARIANT_CLASS_UINT32:
+ g_snprintf (buf, sizeof (buf), "%u", g_variant_get_uint32 (value));
+ type_str = "i8";
+ break;
+ case G_VARIANT_CLASS_INT64:
+ g_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:
+ 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: {
+ /* 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 (!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_UINT64:
+ case G_VARIANT_CLASS_OBJECT_PATH:
+ case G_VARIANT_CLASS_SIGNATURE:
+ 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.
+ *
+ * Serialization details:
+ * - "a{s*}" and "{s*}" are serialized as &lt;struct&gt;
+ * - "ay" is 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 the nonstandard &lt;i8&gt; type
+ * - doubles are serialized as &lt;double&gt;
+ * - Strings are serialized as &lt;string&gt;
+ * - Variants (i.e. "v" type) are unwrapped and their child is serialized.
+ * - #GVariants created by soup_xmlrpc_variant_new_datetime() are serialized as
+ * &lt;dateTime.iso8601&gt;
+ * - Other types are not supported and will return %NULL and set @error.
+ * This notably includes: object-paths, signatures, uint64, handles, maybes
+ * and dictionaries with non-string keys.
+ *
+ * If @params is floating, it is consumed.
+ *
+ * Return value: the text of the methodCall, or %NULL on error.
+ * Since: 2.52
+ **/
+char *
+soup_xmlrpc_build_request (const char *method_name,
+ GVariant *params,
+ GError **error)
+{
+ xmlDoc *doc;
+ xmlNode *node, *param;
+ xmlChar *xmlbody;
+ GVariantIter iter;
+ GVariant *child;
+ int len;
+ char *body = NULL;
+
+ g_return_val_if_fail (g_variant_is_of_type (params, G_VARIANT_TYPE_TUPLE), NULL);
+
+ g_variant_ref_sink (params);
+
+ 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);
+
+ 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.
+ *
+ * Since: 2.52
+ **/
+SoupMessage *
+soup_xmlrpc_message_new (const char *uri,
+ const char *method_name,
+ GVariant *params,
+ GError **error)
+{
+ SoupMessage *msg;
+ char *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, but note
+ * that since a method can only have a single return value, @value
+ * should not be a tuple here (unless the return value is an array).
+ *
+ * If @value is floating, it is consumed.
+ *
+ * Returns: the text of the methodResponse, or %NULL on error.
+ *
+ * Since: 2.52
+ **/
+char *
+soup_xmlrpc_build_response (GVariant *value, GError **error)
+{
+ xmlDoc *doc;
+ xmlNode *node;
+ xmlChar *xmlbody;
+ char *body;
+ int 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 ((char *)xmlbody, len);
+ xmlFree (xmlbody);
+
+ xmlFreeDoc (doc);
+ g_variant_unref (value);
+
+ return body;
+}
+
+char *
+soup_xmlrpc_build_faultv (int fault_code,
+ const char *fault_format,
+ va_list args) G_GNUC_PRINTF (2, 0);
+
+char *
+soup_xmlrpc_build_faultv (int fault_code, const char *fault_format, va_list args)
+{
+ xmlDoc *doc;
+ xmlNode *node, *member;
+ GVariant *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);
+
+ member = xmlNewChild (node, NULL, (const xmlChar *)"member", NULL);
+ xmlNewChild (member, NULL,
+ (const xmlChar *)"name", (const xmlChar *)"faultCode");
+ value = g_variant_new_int32 (fault_code);
+ insert_value (member, value, NULL);
+ g_variant_unref (value);
+
+ member = xmlNewChild (node, NULL, (const xmlChar *)"member", NULL);
+ xmlNewChild (member, NULL,
+ (const xmlChar *)"name", (const xmlChar *)"faultString");
+ value = g_variant_new_take_string (fault_string);
+ insert_value (member, value, NULL);
+ g_variant_unref (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_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.
+ *
+ * Since: 2.52
+ **/
+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));
+}
+
+/**
+ * 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_message_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 *
+signature_get_next_complete_type (const char **signature)
+{
+ GVariantClass class;
+ const char *initial_signature;
+ char *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) {
+ char 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) {
+ 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;
+}
+
+static GVariant *
+parse_array (xmlNode *node, const char **signature, GError **error)
+{
+ 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 ();
+ }
+
+ 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 char **signature,
+ char **entry_signature,
+ char **key_signature,
+ char **value_signature)
+{
+ const char *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 char **signature, GError **error)
+{
+ 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;
+ }
+
+ 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;
+ }
+
+ 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 char *str;
+ char *endptr;
+ gint64 num = 0;
+ guint64 unum = 0;
+ GVariant *variant = NULL;
+
+ content = xmlNodeGetContent (typenode);
+ str = (const char *) 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 char *)typenode->name);
+ goto fail;
+ }
+
+fail:
+ xmlFree (content);
+
+ return variant;
+}
+
+static GVariant *
+parse_double (xmlNode *typenode, GError **error)
+{
+ 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);
+
+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 *
+soup_xmlrpc_variant_new_custom (const char *type, const char *v)
+{
+ return g_variant_new ("(oss)", "/org/gnome/libsoup/ExtensionType",
+ type, v);
+}
+
+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);
+ if (class == G_VARIANT_CLASS_VARIANT || class == G_VARIANT_CLASS_STRING)
+ variant = g_variant_new_string ((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")) {
+ if (class != G_VARIANT_CLASS_VARIANT) {
+ g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+ "<dateTime.iso8601> node does not match signature");
+ goto fail;
+ }
+
+ content = xmlNodeGetContent (typenode);
+ variant = soup_xmlrpc_variant_new_custom ("dateTime.iso8601",
+ (const char *)content);
+ } 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 XML-RPC methodCall parameter values.
+ * Can be parsed using soup_xmlrpc_params_parse() and freed with
+ * soup_xmlrpc_params_free().
+ *
+ * Since: 2.52
+ */
+struct _SoupXMLRPCParams
+{
+ xmlNode *node;
+};
+
+/**
+ * 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);
+
+ 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().
+ *
+ * Deserialization details:
+ * - If @signature is provided, &lt;int&gt; and &lt;i4&gt; can be deserialized
+ * to byte, int16, uint16, int32, uint32, int64 or uint64. Otherwise
+ * it will be deserialized to 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}". @signature could define
+ * another value type (e.g. "a{ss}").
+ * - &lt;array&gt; will be deserialized to "av". @signature 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".
+ * - &lt;dateTime.iso8601&gt; will be deserialized to an unspecified variant
+ * type. If @signature is provided it must have the generic "v" type, which
+ * means there is no guarantee that it's actually a datetime that has been
+ * received. soup_xmlrpc_variant_get_datetime() must be used to parse and
+ * type check this special variant.
+ * - @signature must not have maybes, otherwise an error is returned.
+ * - Dictionaries must have string keys, otherwise an error is returned.
+ *
+ * Returns: (transfer full): a new (non-floating) #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);
+
+ if (!self->node) {
+ if (!signature || g_variant_type_equal (G_VARIANT_TYPE (signature), G_VARIANT_TYPE ("av")))
+ value = g_variant_new_array (G_VARIANT_TYPE_VARIANT, NULL, 0);
+ else if (g_variant_type_equal (G_VARIANT_TYPE (signature), G_VARIANT_TYPE_UNIT))
+ value = g_variant_new_tuple (NULL, 0);
+ else {
+ g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+ "Invalid signature '%s', was expecting '()'", signature);
+ goto fail;
+ }
+ } else {
+ value = parse_value (self->node, signature ? &signature : NULL, error);
+ }
+
+fail:
+ 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().
+ *
+ * Returns: (transfer full): method's name, or %NULL on error.
+ * Since: 2.52
+ **/
+char *
+soup_xmlrpc_parse_request (const char *method_call,
+ int length,
+ SoupXMLRPCParams **params,
+ GError **error)
+{
+ xmlDoc *doc = NULL;
+ xmlNode *node;
+ xmlChar *xmlMethodName = NULL;
+ char *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 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) {
+ 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) {
+ if (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_xmlrpc_params_new (node);
+ doc = NULL;
+ } else {
+ *params = soup_xmlrpc_params_new (NULL);
+ }
+ }
+
+ method_name = g_strdup ((char *)xmlMethodName);
+
+fail:
+ if (doc)
+ xmlFreeDoc (doc);
+ if (xmlMethodName)
+ xmlFree (xmlMethodName);
+
+ 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_params_parse() for deserialization details.
+ *
+ * Returns: (transfer full): a new (non-floating) #GVariant, or %NULL
+ *
+ * Since: 2.52
+ **/
+GVariant *
+soup_xmlrpc_parse_response (const char *method_response,
+ int length,
+ const char *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;
+}
+
+/**
+ * soup_xmlrpc_variant_new_datetime:
+ * @date: a #SoupDate
+ *
+ * Construct a special #GVariant used to serialize a &lt;dateTime.iso8601&gt;
+ * node. See soup_xmlrpc_build_request().
+ *
+ * The actual type of the returned #GVariant is unspecified and "v" or "*"
+ * should be used in variant format strings. For example:
+ * <informalexample><programlisting>
+ * args = g_variant_new ("(v)", soup_xmlrpc_variant_new_datetime (date));
+ * </programlisting></informalexample>
+ *
+ * Returns: a floating #GVariant.
+ *
+ * Since: 2.52
+ */
+GVariant *
+soup_xmlrpc_variant_new_datetime (SoupDate *date)
+{
+ GVariant *variant;
+ char *str;
+
+ str = soup_date_to_string (date, SOUP_DATE_ISO8601_XMLRPC);
+ variant = soup_xmlrpc_variant_new_custom ("dateTime.iso8601", str);
+ g_free (str);
+
+ return variant;
+}
+
+/**
+ * soup_xmlrpc_variant_get_datetime:
+ * @variant: a #GVariant
+ * @error: a #GError, or %NULL
+ *
+ * Get the #SoupDate from special #GVariant created by
+ * soup_xmlrpc_variant_new_datetime() or by parsing a &lt;dateTime.iso8601&gt;
+ * node. See soup_xmlrpc_params_parse().
+ *
+ * If @variant does not contain a datetime it will return an error but it is not
+ * considered a programmer error because it generally means parameters received
+ * are not in the expected type.
+ *
+ * Returns: a new #SoupDate, or %NULL on error.
+ *
+ * Since: 2.52
+ */
+SoupDate *
+soup_xmlrpc_variant_get_datetime (GVariant *variant, GError **error)
+{
+ SoupDate *date = NULL;
+ const char *path;
+ const char *type;
+ const char *v;
+
+ if (!g_variant_is_of_type (variant, G_VARIANT_TYPE ("(oss)"))) {
+ g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+ "Variant is of type '%s' which is not expected for a datetime",
+ g_variant_get_type_string (variant));
+ return NULL;
+ }
+
+ g_variant_get (variant, "(&o&s&s)", &path, &type, &v);
+
+ if (!g_str_equal (path, "/org/gnome/libsoup/ExtensionType") ||
+ !g_str_equal (type, "dateTime.iso8601")) {
+ g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+ "Variant doesn't represent a datetime: %s",
+ g_variant_get_type_string (variant));
+ return NULL;
+ }
+
+ date = soup_date_new_from_string (v);
+
+ if (date == NULL) {
+ g_set_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS,
+ "Can't parse datetime string: %s", v);
+ return NULL;
+ }
+
+ return date;
+
+}
+
+/**
+ * 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.
+ */
+
+G_DEFINE_QUARK (soup_xmlrpc_fault_quark, soup_xmlrpc_fault);
+G_DEFINE_QUARK (soup_xmlrpc_error_quark, soup_xmlrpc_error);
diff --git a/libsoup/soup-xmlrpc.h b/libsoup/soup-xmlrpc.h
new file mode 100644
index 00000000..89d99a16
--- /dev/null
+++ b/libsoup/soup-xmlrpc.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright 2015 - Collabora Ltd.
+ */
+
+#ifndef SOUP_XMLRPC_H
+#define SOUP_XMLRPC_H 1
+
+#include <libsoup/soup-types.h>
+#include <libsoup/soup-xmlrpc-old.h>
+
+G_BEGIN_DECLS
+
+/* XML-RPC client */
+SOUP_AVAILABLE_IN_2_52
+char *soup_xmlrpc_build_request (const char *method_name,
+ GVariant *params,
+ GError **error);
+SOUP_AVAILABLE_IN_2_52
+SoupMessage *soup_xmlrpc_message_new (const char *uri,
+ const char *method_name,
+ GVariant *params,
+ GError **error);
+SOUP_AVAILABLE_IN_2_52
+GVariant *soup_xmlrpc_parse_response (const char *method_response,
+ int length,
+ const char *signature,
+ GError **error);
+
+/* XML-RPC server */
+typedef struct _SoupXMLRPCParams SoupXMLRPCParams;
+SOUP_AVAILABLE_IN_2_52
+void soup_xmlrpc_params_free (SoupXMLRPCParams *self);
+SOUP_AVAILABLE_IN_2_52
+GVariant *soup_xmlrpc_params_parse (SoupXMLRPCParams *self,
+ const char *signature,
+ GError **error);
+SOUP_AVAILABLE_IN_2_52
+char *soup_xmlrpc_parse_request (const char *method_call,
+ int length,
+ SoupXMLRPCParams **params,
+ GError **error);
+SOUP_AVAILABLE_IN_2_52
+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);
+SOUP_AVAILABLE_IN_2_52
+gboolean soup_xmlrpc_message_set_response (SoupMessage *msg,
+ GVariant *value,
+ GError **error);
+SOUP_AVAILABLE_IN_2_52
+void soup_xmlrpc_message_set_fault (SoupMessage *msg,
+ int fault_code,
+ const char *fault_format,
+ ...) G_GNUC_PRINTF (3, 4);
+
+/* Utils */
+SOUP_AVAILABLE_IN_2_52
+GVariant *soup_xmlrpc_variant_new_datetime (SoupDate *date);
+
+SOUP_AVAILABLE_IN_2_52
+SoupDate *soup_xmlrpc_variant_get_datetime (GVariant *variant,
+ GError **error);
+
+/* 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_H */
diff --git a/libsoup/soup.h b/libsoup/soup.h
index 8790b172..7c5f1ba7 100644
--- a/libsoup/soup.h
+++ b/libsoup/soup.h
@@ -52,6 +52,7 @@ extern "C" {
#include <libsoup/soup-version.h>
#include <libsoup/soup-websocket.h>
#include <libsoup/soup-websocket-connection.h>
+#include <libsoup/soup-xmlrpc.h>
#include <libsoup/soup-xmlrpc-old.h>
#ifdef __cplusplus
diff --git a/tests/Makefile.am b/tests/Makefile.am
index f2f9f0c4..33f29df5 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -43,7 +43,9 @@ test_programs = \
uri-parsing \
websocket-test \
xmlrpc-old-server-test \
- xmlrpc-old-test
+ xmlrpc-old-test \
+ xmlrpc-server-test \
+ xmlrpc-test
test_extra_programs = \
ntlm-test-helper \
diff --git a/tests/xmlrpc-old-server-test.c b/tests/xmlrpc-old-server-test.c
index eb73f570..3d3ba992 100644
--- a/tests/xmlrpc-old-server-test.c
+++ b/tests/xmlrpc-old-server-test.c
@@ -355,7 +355,7 @@ main (int argc, char **argv)
} else {
GMainLoop *loop;
- g_print ("Listening on port %d\n", soup_server_get_port (server));
+ g_print ("Listening on port %d\n", server_uri->port);
loop = g_main_loop_new (NULL, TRUE);
g_main_loop_run (loop);
diff --git a/tests/xmlrpc-server-test.c b/tests/xmlrpc-server-test.c
new file mode 100644
index 00000000..db390cc5
--- /dev/null
+++ b/tests/xmlrpc-server-test.c
@@ -0,0 +1,374 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright 2008 Red Hat, Inc.
+ * Copyright 2015, Collabora ltd.
+ */
+
+#include "test-utils.h"
+
+static char *uri;
+
+static GVariant *
+parse_params (SoupMessage *msg, SoupXMLRPCParams *params, const char *signature)
+{
+ GVariant *args;
+ GError *error = NULL;
+
+ args = soup_xmlrpc_params_parse (params, signature, &error);
+ if (!args) {
+ soup_xmlrpc_message_set_fault (msg,
+ SOUP_XMLRPC_FAULT_SERVER_ERROR_INVALID_METHOD_PARAMETERS,
+ "Wrong method signature: expected %s: %s",
+ signature, error->message);
+ }
+
+ return args;
+}
+
+static void
+do_sum (SoupMessage *msg, SoupXMLRPCParams *params)
+{
+ GVariant *args;
+ GVariant *child;
+ GVariantIter iter;
+ int sum = 0, val;
+
+ if (!(args = parse_params (msg, params, "(ai)")))
+ return;
+
+ child = g_variant_get_child_value (args, 0);
+
+ g_variant_iter_init (&iter, child);
+ while (g_variant_iter_loop (&iter, "i", &val))
+ sum += val;
+
+ soup_xmlrpc_message_set_response (msg, g_variant_new_int32 (sum), NULL);
+
+ g_variant_unref (args);
+ g_variant_unref (child);
+}
+
+static void
+do_countBools (SoupMessage *msg, SoupXMLRPCParams *params)
+{
+ GVariant *args;
+ GVariant *child;
+ GVariantIter iter;
+ gboolean val;
+ int trues = 0, falses = 0;
+ GVariantDict dict;
+
+ if (!(args = parse_params (msg, params, "(ab)")))
+ return;
+
+ child = g_variant_get_child_value (args, 0);
+
+ g_variant_iter_init (&iter, child);
+ while (g_variant_iter_loop (&iter, "b", &val)) {
+ if (val)
+ trues++;
+ else
+ falses++;
+ }
+
+ g_variant_dict_init (&dict, NULL);
+ g_variant_dict_insert (&dict, "true", "i", trues);
+ g_variant_dict_insert (&dict, "false", "i", falses);
+
+ soup_xmlrpc_message_set_response (msg, g_variant_dict_end (&dict), NULL);
+
+ g_variant_unref (args);
+ g_variant_unref (child);
+}
+
+static void
+do_md5sum (SoupMessage *msg, SoupXMLRPCParams *params)
+{
+ GVariant *args;
+ GVariant *child;
+ GChecksum *checksum;
+ GByteArray *digest;
+ gsize digest_len = 16;
+
+ if (!(args = parse_params (msg, params, "(ay)")))
+ return;
+
+ child = g_variant_get_child_value (args, 0);
+
+ checksum = g_checksum_new (G_CHECKSUM_MD5);
+ g_checksum_update (checksum,
+ g_variant_get_data (child),
+ g_variant_get_size (child));
+ digest = g_byte_array_new ();
+ g_byte_array_set_size (digest, digest_len);
+ g_checksum_get_digest (checksum, digest->data, &digest_len);
+ g_checksum_free (checksum);
+
+ soup_xmlrpc_message_set_response (msg,
+ g_variant_new_from_data (G_VARIANT_TYPE_BYTESTRING,
+ digest->data, digest_len,
+ TRUE, NULL, NULL),
+ NULL);
+ g_byte_array_free (digest, TRUE);
+ g_variant_unref (child);
+ g_variant_unref (args);
+}
+
+
+static void
+do_dateChange (SoupMessage *msg, SoupXMLRPCParams *params)
+{
+ GVariant *args;
+ GVariant *timestamp;
+ SoupDate *date;
+ GVariant *arg;
+ int val;
+ GError *error = NULL;
+
+ if (!(args = parse_params (msg, params, "(va{si})")))
+ return;
+
+ g_variant_get (args, "(v@a{si})", &timestamp, &arg);
+
+ date = soup_xmlrpc_variant_get_datetime (timestamp, &error);
+ if (!date) {
+ soup_xmlrpc_message_set_fault (msg,
+ SOUP_XMLRPC_FAULT_SERVER_ERROR_INVALID_METHOD_PARAMETERS,
+ "%s", error->message);
+ g_clear_error (&error);
+ goto fail;
+ }
+
+ if (g_variant_lookup (arg, "tm_year", "i", &val))
+ date->year = val + 1900;
+ if (g_variant_lookup (arg, "tm_mon", "i", &val))
+ date->month = val + 1;
+ if (g_variant_lookup (arg, "tm_mday", "i", &val))
+ date->day = val;
+ if (g_variant_lookup (arg, "tm_hour", "i", &val))
+ date->hour = val;
+ if (g_variant_lookup (arg, "tm_min", "i", &val))
+ date->minute = val;
+ if (g_variant_lookup (arg, "tm_sec", "i", &val))
+ date->second = val;
+
+ soup_xmlrpc_message_set_response (msg,
+ soup_xmlrpc_variant_new_datetime (date),
+ NULL);
+
+ soup_date_free (date);
+
+fail:
+ g_variant_unref (args);
+ g_variant_unref (arg);
+ g_variant_unref (timestamp);
+}
+
+static void
+do_echo (SoupMessage *msg, SoupXMLRPCParams *params)
+{
+ GVariant *args;
+ GVariant *child;
+
+ if (!(args = parse_params (msg, params, "(as)")))
+ return;
+
+ child = g_variant_get_child_value (args, 0);
+ soup_xmlrpc_message_set_response (msg, child, NULL);
+ g_variant_unref (args);
+ g_variant_unref (child);
+}
+
+static void
+do_ping (SoupMessage *msg, SoupXMLRPCParams *params)
+{
+ GVariant *args;
+
+ if (!(args = parse_params (msg, params, "()")))
+ return;
+
+ soup_xmlrpc_message_set_response (msg, g_variant_new_string ("pong"), NULL);
+ g_variant_unref (args);
+}
+
+static void
+server_callback (SoupServer *server, SoupMessage *msg,
+ const char *path, GHashTable *query,
+ SoupClientContext *context, gpointer data)
+{
+ char *method_name;
+ SoupXMLRPCParams *params;
+ GError *error = NULL;
+
+ if (msg->method != SOUP_METHOD_POST) {
+ soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
+ return;
+ }
+
+ soup_message_set_status (msg, SOUP_STATUS_OK);
+
+ method_name = soup_xmlrpc_parse_request (msg->request_body->data,
+ msg->request_body->length,
+ &params, &error);
+ if (!method_name) {
+ soup_xmlrpc_message_set_fault (msg, SOUP_XMLRPC_FAULT_PARSE_ERROR_NOT_WELL_FORMED,
+ "Could not parse method call: %s", error->message);
+ g_clear_error (&error);
+ return;
+ }
+
+ if (!strcmp (method_name, "sum"))
+ do_sum (msg, params);
+ else if (!strcmp (method_name, "countBools"))
+ do_countBools (msg, params);
+ else if (!strcmp (method_name, "md5sum"))
+ do_md5sum (msg, params);
+ else if (!strcmp (method_name, "dateChange"))
+ do_dateChange (msg, params);
+ else if (!strcmp (method_name, "echo"))
+ do_echo (msg, params);
+ else if (!strcmp (method_name, "ping"))
+ do_ping (msg, params);
+ else {
+ soup_xmlrpc_message_set_fault (msg, SOUP_XMLRPC_FAULT_SERVER_ERROR_REQUESTED_METHOD_NOT_FOUND,
+ "Unknown method %s", method_name);
+ }
+
+ g_free (method_name);
+ soup_xmlrpc_params_free (params);
+}
+
+static gboolean
+run_xmlrpc_test (char **argv,
+ char **stdout_out,
+ char **stderr_out,
+ GError **error)
+{
+ gboolean ok;
+ int status;
+
+ argv[0] = g_test_build_filename (G_TEST_BUILT, "xmlrpc-test", NULL);
+ ok = g_spawn_sync (NULL, argv, NULL, 0, NULL, NULL,
+ stdout_out, stderr_out, &status,
+ error);
+ g_free (argv[0]);
+
+ if (!ok)
+ return FALSE;
+
+ return g_spawn_check_exit_status (status, error);
+}
+
+static void
+do_one_xmlrpc_test (gconstpointer data)
+{
+ const char *path = data;
+ char *argv[12];
+ char *stdout_out, *stderr_out;
+ GError *error = NULL;
+ int arg;
+
+ argv[0] = NULL;
+ argv[1] = "-S";
+ argv[2] = "-U";
+ argv[3] = uri;
+ argv[4] = "-q";
+ argv[5] = "-p";
+ argv[6] = (char *) path;
+
+ for (arg = 0; arg < debug_level && arg < 3; arg++)
+ argv[arg + 7] = "-d";
+ argv[arg + 7] = NULL;
+
+ run_xmlrpc_test (argv, &stdout_out, &stderr_out, &error);
+ if (stdout_out) {
+ g_print ("%s", stdout_out);
+ g_free (stdout_out);
+ }
+ if (stderr_out) {
+ g_printerr ("%s", stderr_out);
+ g_free (stderr_out);
+ }
+
+ if ( g_error_matches (error, G_SPAWN_EXIT_ERROR, 1)
+ || g_error_matches (error, G_SPAWN_EXIT_ERROR, 77))
+ g_test_fail ();
+ else
+ g_assert_no_error (error);
+ g_clear_error (&error);
+}
+
+gboolean run_tests = TRUE;
+
+static GOptionEntry no_test_entry[] = {
+ { "no-tests", 'n', G_OPTION_FLAG_REVERSE,
+ G_OPTION_ARG_NONE, &run_tests,
+ "Don't run tests, just run the test server", NULL },
+ { NULL }
+};
+
+int
+main (int argc, char **argv)
+{
+ SoupServer *server;
+ SoupURI *server_uri;
+ int ret;
+
+ test_init (argc, argv, no_test_entry);
+
+ server = soup_test_server_new (run_tests ? SOUP_TEST_SERVER_IN_THREAD : SOUP_TEST_SERVER_DEFAULT);
+ soup_server_add_handler (server, "/xmlrpc-server.php",
+ server_callback, NULL, NULL);
+ server_uri = soup_test_server_get_uri (server, "http", NULL);
+ soup_uri_set_path (server_uri, "/xmlrpc-server.php");
+ uri = soup_uri_to_string (server_uri, FALSE);
+
+ if (run_tests) {
+ char *out, **tests, *path;
+ char *list_argv[4];
+ GError *error = NULL;
+ int i;
+
+ list_argv[0] = NULL;
+ list_argv[1] = "-S";
+ list_argv[2] = "-l";
+ list_argv[3] = NULL;
+
+ if (!run_xmlrpc_test (list_argv, &out, NULL, &error)) {
+ g_printerr ("'xmlrpc-test -l' failed: %s\n", error->message);
+ g_error_free (error);
+ return 1;
+ }
+
+ tests = g_strsplit (out, "\n", -1);
+ g_free (out);
+
+ for (i = 0; tests[i] && *tests[i]; i++) {
+ g_assert_true (g_str_has_prefix (tests[i], "/xmlrpc/"));
+ path = g_strdup_printf ("/xmlrpc-server/%s", tests[i] + strlen ("/xmlrpc/"));
+ g_test_add_data_func (path, tests[i], do_one_xmlrpc_test);
+ g_free (path);
+ }
+
+ ret = g_test_run ();
+
+ g_strfreev (tests);
+ } else {
+ GMainLoop *loop;
+
+ g_print ("Listening on port %d\n", server_uri->port);
+
+ loop = g_main_loop_new (NULL, TRUE);
+ g_main_loop_run (loop);
+ g_main_loop_unref (loop);
+
+ ret = 0;
+ }
+
+ soup_test_server_quit_unref (server);
+ soup_uri_free (server_uri);
+ g_free (uri);
+ if (run_tests)
+ test_cleanup ();
+ return ret;
+}
diff --git a/tests/xmlrpc-test.c b/tests/xmlrpc-test.c
new file mode 100644
index 00000000..c50995aa
--- /dev/null
+++ b/tests/xmlrpc-test.c
@@ -0,0 +1,749 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright 2001-2003, Ximian, Inc.
+ * Copyright 2015, Collabora ltd.
+ */
+
+#include "test-utils.h"
+
+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, const char *signature, GVariant **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);
+
+ *retval = soup_xmlrpc_parse_response (msg->response_body->data,
+ msg->response_body->length,
+ signature, &err);
+ if (!*retval) {
+ if (err->domain == SOUP_XMLRPC_FAULT)
+ soup_test_assert (FALSE, "FAULT: %d %s\n", err->code, err->message);
+ else
+ soup_test_assert (FALSE, "ERROR: %s\n", err->message);
+ g_error_free (err);
+ g_object_unref (msg);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+do_xmlrpc (const char *method, GVariant *args, const char *signature, GVariant **retval)
+{
+ gboolean ret;
+ char *body;
+ GError *error = NULL;
+
+ body = soup_xmlrpc_build_request (method, args, &error);
+ g_assert_no_error (error);
+ if (!body)
+ return FALSE;
+
+ ret = send_xmlrpc (body, signature, retval);
+ g_free (body);
+
+ return ret;
+}
+
+static void
+test_sum (void)
+{
+ GVariantBuilder builder;
+ int i, val, sum, result;
+ GVariant *retval;
+ gboolean ok;
+
+ SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER;
+
+ debug_printf (2, "sum (array of int -> int): ");
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("ai"));
+ for (i = sum = 0; i < 10; i++) {
+ val = g_random_int_range (0, 100);
+ debug_printf (2, "%s%d", i == 0 ? "[" : ", ", val);
+ g_variant_builder_add (&builder, "i", val);
+ sum += val;
+ }
+ debug_printf (2, "] -> ");
+
+ ok = do_xmlrpc ("sum",
+ g_variant_new ("(@ai)", g_variant_builder_end (&builder)),
+ "i", &retval);
+
+ if (!ok)
+ return;
+
+ result = g_variant_get_int32 (retval);
+ debug_printf (2, "%d\n", result);
+ g_assert_cmpint (result, ==, sum);
+
+ g_variant_unref (retval);
+}
+
+static void
+test_countBools (void)
+{
+ GVariantBuilder builder;
+ int i, trues, falses;
+ GVariant *retval;
+ int ret_trues, ret_falses;
+ gboolean val, ok;
+
+ SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER;
+
+ debug_printf (2, "countBools (array of boolean -> struct of ints): ");
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("ab"));
+ for (i = trues = falses = 0; i < 10; i++) {
+ val = g_random_boolean ();
+ debug_printf (2, "%s%c", i == 0 ? "[" : ", ", val ? 'T' : 'F');
+ g_variant_builder_add (&builder, "b", val);
+ if (val)
+ trues++;
+ else
+ falses++;
+ }
+ debug_printf (2, "] -> ");
+
+ ok = do_xmlrpc ("countBools",
+ g_variant_new ("(@ab)", g_variant_builder_end (&builder)),
+ "a{si}", &retval);
+ if (!ok)
+ return;
+
+ g_assert_true (g_variant_lookup (retval, "true", "i", &ret_trues));
+ g_assert_true (g_variant_lookup (retval, "false", "i", &ret_falses));
+ g_assert_cmpint (g_variant_n_children (retval), ==, 2);
+ g_variant_unref (retval);
+
+ 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;
+ int i;
+ GChecksum *checksum;
+ guchar digest[16];
+ gsize digest_len = sizeof (digest);
+ GVariant *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",
+ g_variant_new ("(@ay)",
+ g_variant_new_from_data (G_VARIANT_TYPE_BYTESTRING,
+ data->data, data->len,
+ TRUE, NULL, NULL)),
+ "ay", &retval);
+ g_byte_array_free (data, TRUE);
+ if (!ok)
+ return;
+
+ soup_assert_cmpmem (g_variant_get_data (retval), g_variant_get_size (retval),
+ digest, digest_len);
+ g_variant_unref (retval);
+}
+
+static void
+test_dateChange (void)
+{
+ GVariantDict structval;
+ SoupDate *date, *result;
+ char *timestamp;
+ GVariant *retval;
+ gboolean ok;
+ GError *error = NULL;
+
+ 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) {
+ char *tmp;
+
+ tmp = soup_date_to_string (date, SOUP_DATE_ISO8601_XMLRPC);
+ debug_printf (2, "date: %s, {", tmp);
+ g_free (tmp);
+ }
+
+ g_variant_dict_init (&structval, NULL);
+
+#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);
+ g_variant_dict_insert (&structval, "tm_year",
+ "i", date->year - 1900);
+ }
+ if (MAYBE) {
+ date->month = 1 + g_random_int_range (0, 12);
+ debug_printf (2, "tm_mon: %d, ", date->month - 1);
+ g_variant_dict_insert (&structval, "tm_mon",
+ "i", date->month - 1);
+ }
+ if (MAYBE) {
+ date->day = 1 + g_random_int_range (0, 28);
+ debug_printf (2, "tm_mday: %d, ", date->day);
+ g_variant_dict_insert (&structval, "tm_mday",
+ "i", date->day);
+ }
+ if (MAYBE) {
+ date->hour = g_random_int_range (0, 24);
+ debug_printf (2, "tm_hour: %d, ", date->hour);
+ g_variant_dict_insert (&structval, "tm_hour",
+ "i", date->hour);
+ }
+ if (MAYBE) {
+ date->minute = g_random_int_range (0, 60);
+ debug_printf (2, "tm_min: %d, ", date->minute);
+ g_variant_dict_insert (&structval, "tm_min",
+ "i", date->minute);
+ }
+ if (MAYBE) {
+ date->second = g_random_int_range (0, 60);
+ debug_printf (2, "tm_sec: %d, ", date->second);
+ g_variant_dict_insert (&structval, "tm_sec",
+ "i", date->second);
+ }
+
+ debug_printf (2, "} -> ");
+
+ ok = do_xmlrpc ("dateChange",
+ g_variant_new ("(vv)",
+ soup_xmlrpc_variant_new_datetime (date),
+ g_variant_dict_end (&structval)),
+ NULL, &retval);
+ if (!ok) {
+ soup_date_free (date);
+ return;
+ }
+
+ result = soup_xmlrpc_variant_get_datetime (retval, &error);
+ g_assert_no_error (error);
+
+ 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);
+ g_variant_unref (retval);
+}
+
+static const char *const echo_strings[] = {
+ "This is a test",
+ "& so is this",
+ "and so is <this>",
+ "&amp; so is &lt;this&gt;",
+ NULL
+};
+
+static void
+test_echo (void)
+{
+ GVariant *originals;
+ GVariant *retval;
+ char *str;
+
+ SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER;
+
+ debug_printf (2, "echo (array of string -> array of string):\n");
+
+ originals = g_variant_new ("^as", echo_strings);
+ g_variant_ref_sink (originals);
+ str = g_variant_print (originals, TRUE);
+ debug_printf (2, "%s -> ", str);
+ g_free (str);
+
+ if (!do_xmlrpc ("echo",
+ g_variant_new ("(@as)", originals),
+ "as", &retval)) {
+ g_variant_unref (originals);
+ return;
+ }
+
+ str = g_variant_print (retval, TRUE);
+ debug_printf (2, "%s\n", str);
+ g_free (str);
+
+ g_assert_true (g_variant_equal (originals, retval));
+
+ g_variant_unref (originals);
+ g_variant_unref (retval);
+}
+
+static void
+test_ping (gconstpointer include_params)
+{
+ GVariant *retval;
+ char *request;
+ gboolean ret;
+ GError *error = NULL;
+
+ 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>");
+
+ request = soup_xmlrpc_build_request ("ping", g_variant_new ("()"), &error);
+ g_assert_no_error (error);
+ 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, "s", &retval);
+ g_free (request);
+
+ if (!ret)
+ return;
+
+ g_assert_cmpstr (g_variant_get_string (retval, NULL), ==, "pong");
+ g_variant_unref (retval);
+}
+
+static void
+do_bad_xmlrpc (const char *body)
+{
+ 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_response (msg->response_body->data,
+ msg->response_body->length,
+ "()", &err)) {
+ if (err->domain == SOUP_XMLRPC_FAULT) {
+ 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: %s\n", err->message);
+ } 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>");
+}
+
+#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 char *expected_params)
+{
+ char *debug;
+ char *body;
+ char *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)
+{
+ char *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)
+{
+ SoupDate *date;
+
+ 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>");
+ date = soup_date_new_from_time_t (1434161309);
+ verify_serialization (g_variant_new ("(v)", soup_xmlrpc_variant_new_datetime (date)),
+ "<params>"
+ "<param><value><dateTime.iso8601>20150613T02:08:29</dateTime.iso8601></value></param>"
+ "</params>");
+ soup_date_free (date);
+ 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, 2},)"));
+ verify_serialization_fail (g_variant_new ("(mi)", NULL));
+ verify_serialization_fail (g_variant_new ("(t)", 0));
+}
+
+static void
+verify_deserialization (GVariant *expected_variant,
+ const char *signature,
+ const char *params)
+{
+ char *body;
+ char *method_name;
+ SoupXMLRPCParams *out_params = NULL;
+ GVariant *variant;
+ GError *error = NULL;
+
+ body = g_strconcat (BODY_PREFIX, params, BODY_SUFFIX, NULL);
+ method_name = soup_xmlrpc_parse_request (body, strlen (body),
+ &out_params,
+ &error);
+ g_assert_no_error (error);
+ g_assert_cmpstr (method_name, ==, "MyMethod");
+
+ variant = soup_xmlrpc_params_parse (out_params, signature, &error);
+ g_assert_no_error (error);
+
+ 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);
+ }
+
+ soup_xmlrpc_params_free (out_params);
+ g_variant_unref (variant);
+ g_free (method_name);
+ g_free (body);
+}
+
+static void
+verify_deserialization_fail (const char *signature,
+ const char *params)
+{
+ char *body;
+ char *method_name;
+ SoupXMLRPCParams *out_params = NULL;
+ GVariant *variant;
+ GError *error = NULL;
+
+ body = g_strconcat (BODY_PREFIX, params, BODY_SUFFIX, NULL);
+ method_name = soup_xmlrpc_parse_request (body, strlen (body),
+ &out_params,
+ &error);
+ g_assert_no_error (error);
+ g_assert_cmpstr (method_name, ==, "MyMethod");
+
+ variant = soup_xmlrpc_params_parse (out_params, signature, &error);
+ g_assert_error (error, SOUP_XMLRPC_ERROR, SOUP_XMLRPC_ERROR_ARGUMENTS);
+ g_assert (variant == NULL);
+
+ g_free (body);
+ soup_xmlrpc_params_free (out_params);
+}
+
+static void
+test_deserializer (void)
+{
+ char *tmp;
+ SoupDate *date;
+
+ 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>");
+ date = soup_date_new_from_time_t (1434146909);
+ verify_deserialization (g_variant_new_parsed ("[%v]", soup_xmlrpc_variant_new_datetime (date)),
+ NULL,
+ "<params>"
+ "<param><value><dateTime.iso8601>20150612T22:08:29</dateTime.iso8601></value></param>"
+ "</params>");
+ soup_date_free (date);
+ verify_deserialization (g_variant_new_parsed ("[<b'bytestring'>]"),
+ NULL,
+ "<params>"
+ "<param><value><base64>Ynl0ZXN0cmluZwA=</base64></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 (NULL,
+ "<params>"
+ "<param><value><boolean>2</boolean></value></param>"
+ "</params>");
+ verify_deserialization_fail ("(y)",
+ "<params>"
+ "<param><value><int>256</int></value></param>"
+ "</params>");
+ verify_deserialization_fail ("(ii)",
+ "<params>"
+ "<param><value><int>1</int></value></param>"
+ "</params>");
+ verify_deserialization_fail ("(i)",
+ "<params>"
+ "<param><value><int>1</int></value></param>"
+ "<param><value><int>2</int></value></param>"
+ "</params>");
+}
+
+static void
+test_fault (void)
+{
+ char *body;
+ GVariant *reply;
+ GError *error = NULL;
+
+ 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);
+
+ 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/variant/serializer", test_serializer);
+ g_test_add_func ("/xmlrpc/variant/deserializer", test_deserializer);
+ g_test_add_func ("/xmlrpc/variant/fault", test_fault);
+ g_test_add_func ("/xmlrpc/variant/sum", test_sum);
+ g_test_add_func ("/xmlrpc/variant/countBools", test_countBools);
+ g_test_add_func ("/xmlrpc/variant/md5sum", test_md5sum);
+ g_test_add_func ("/xmlrpc/variant/dateChange", test_dateChange);
+ g_test_add_func ("/xmlrpc/variant/echo", test_echo);
+ g_test_add_data_func ("/xmlrpc/variant/ping/empty-params", GINT_TO_POINTER (TRUE), test_ping);
+ g_test_add_data_func ("/xmlrpc/variant/ping/no-params", GINT_TO_POINTER (FALSE), test_ping);
+ g_test_add_func ("/xmlrpc/variant/fault/malformed", test_fault_malformed);
+ g_test_add_func ("/xmlrpc/variant/fault/method", test_fault_method);
+ g_test_add_func ("/xmlrpc/variant/fault/args", test_fault_args);
+
+ ret = g_test_run ();
+
+ soup_test_session_abort_unref (session);
+
+ test_cleanup ();
+ return ret;
+}