diff options
author | Xavier Claessens <xavier.claessens@collabora.com> | 2015-07-27 15:52:17 -0400 |
---|---|---|
committer | Xavier Claessens <xavier.claessens@collabora.com> | 2015-08-13 11:28:39 -0400 |
commit | 2dd764906f7d7403b6c47c3e339177085f540c1b (patch) | |
tree | e9e081f7a057475f45234f29ad2e9e15c62658da | |
parent | b9c0435d72cfdb0ca615181a8fad6ebe5fa8363e (diff) | |
download | libsoup-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.txt | 27 | ||||
-rw-r--r-- | libsoup/Makefile.am | 2 | ||||
-rw-r--r-- | libsoup/libsoup-2.4.sym | 11 | ||||
-rw-r--r-- | libsoup/soup-xmlrpc-old.c | 138 | ||||
-rw-r--r-- | libsoup/soup-xmlrpc-old.h | 29 | ||||
-rw-r--r-- | libsoup/soup-xmlrpc.c | 1479 | ||||
-rw-r--r-- | libsoup/soup-xmlrpc.h | 94 | ||||
-rw-r--r-- | libsoup/soup.h | 1 | ||||
-rw-r--r-- | tests/Makefile.am | 4 | ||||
-rw-r--r-- | tests/xmlrpc-old-server-test.c | 2 | ||||
-rw-r--r-- | tests/xmlrpc-server-test.c | 374 | ||||
-rw-r--r-- | tests/xmlrpc-test.c | 749 |
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 <struct> + * - "ay" is serialized as <base64> + * - Other arrays and tuples are serialized as <array> + * - booleans are serialized as <boolean> + * - byte, int16, uint16 and int32 are serialized as <int> + * - uint32 and int64 are serialized as the nonstandard <i8> type + * - doubles are serialized as <double> + * - Strings are serialized as <string> + * - Variants (i.e. "v" type) are unwrapped and their child is serialized. + * - #GVariants created by soup_xmlrpc_variant_new_datetime() are serialized as + * <dateTime.iso8601> + * - 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, <int> and <i4> 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. + * - <struct> will be deserialized to "a{sv}". @signature could define + * another value type (e.g. "a{ss}"). + * - <array> will be deserialized to "av". @signature could define + * another element type (e.g. "as") or could be a tuple (e.g. "(ss)"). + * - <base64> will be deserialized to "ay". + * - <string> will be deserialized to "s". + * - <dateTime.iso8601> 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 <dateTime.iso8601> + * 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 <dateTime.iso8601> + * 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})", ×tamp, &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, + ¶ms, &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>", + "& so is <this>", + 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><>&</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><>&</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; +} |