1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * soup-xmlrpc.c: XML-RPC parser/generator
5 * Copyright (C) 2007 Red Hat, Inc.
15 #include <libxml/tree.h>
17 #include "soup-xmlrpc.h"
18 #include "soup-value-utils.h"
19 #include "soup-date.h"
20 #include "soup-message.h"
21 #include "soup-misc.h"
22 #include "soup-session.h"
26 * @short_description: XML-RPC support
30 static xmlNode *find_real_node (xmlNode *node);
32 static gboolean insert_value (xmlNode *parent, GValue *value);
35 insert_member (gpointer name, gpointer value, gpointer data)
37 xmlNode *member, **struct_node = data;
42 member = xmlNewChild (*struct_node, NULL,
43 (const xmlChar *)"member", NULL);
44 xmlNewTextChild (member, NULL,
45 (const xmlChar *)"name", (const xmlChar *)name);
46 if (!insert_value (member, value)) {
47 xmlFreeNode (*struct_node);
53 insert_value (xmlNode *parent, GValue *value)
55 GType type = G_VALUE_TYPE (value);
59 xvalue = xmlNewChild (parent, NULL, (const xmlChar *)"value", NULL);
61 if (type == G_TYPE_INT) {
62 snprintf (buf, sizeof (buf), "%d", g_value_get_int (value));
63 xmlNewChild (xvalue, NULL,
64 (const xmlChar *)"int",
65 (const xmlChar *)buf);
66 } else if (type == G_TYPE_BOOLEAN) {
67 snprintf (buf, sizeof (buf), "%d", g_value_get_boolean (value));
68 xmlNewChild (xvalue, NULL,
69 (const xmlChar *)"boolean",
70 (const xmlChar *)buf);
71 } else if (type == G_TYPE_STRING) {
72 xmlNewTextChild (xvalue, NULL,
73 (const xmlChar *)"string",
74 (const xmlChar *)g_value_get_string (value));
75 } else if (type == G_TYPE_DOUBLE) {
76 g_ascii_dtostr (buf, sizeof (buf), g_value_get_double (value));
77 xmlNewChild (xvalue, NULL,
78 (const xmlChar *)"double",
79 (const xmlChar *)buf);
80 } else if (type == SOUP_TYPE_DATE) {
81 SoupDate *date = g_value_get_boxed (value);
82 char *timestamp = soup_date_to_string (date, SOUP_DATE_ISO8601_XMLRPC);
83 xmlNewChild (xvalue, NULL,
84 (const xmlChar *)"dateTime.iso8601",
85 (const xmlChar *)timestamp);
87 } else if (type == SOUP_TYPE_BYTE_ARRAY) {
88 GByteArray *ba = g_value_get_boxed (value);
91 encoded = g_base64_encode (ba->data, ba->len);
92 xmlNewChild (xvalue, NULL,
93 (const xmlChar *)"base64",
94 (const xmlChar *)encoded);
96 } else if (type == G_TYPE_HASH_TABLE) {
97 GHashTable *hash = g_value_get_boxed (value);
100 struct_node = xmlNewChild (xvalue, NULL,
101 (const xmlChar *)"struct", NULL);
102 g_hash_table_foreach (hash, insert_member, &struct_node);
105 } else if (type == G_TYPE_VALUE_ARRAY) {
106 GValueArray *va = g_value_get_boxed (value);
110 node = xmlNewChild (xvalue, NULL,
111 (const xmlChar *)"array", NULL);
112 node = xmlNewChild (node, NULL,
113 (const xmlChar *)"data", NULL);
114 for (i = 0; i < va->n_values; i++) {
115 if (!insert_value (node, &va->values[i]))
125 * soup_xmlrpc_build_method_call:
126 * @method_name: the name of the XML-RPC method
127 * @params: (array length=n_params): arguments to @method
128 * @n_params: length of @params
130 * This creates an XML-RPC methodCall and returns it as a string.
131 * This is the low-level method that soup_xmlrpc_request_new() and
132 * soup_xmlrpc_call() are built on.
134 * @params is an array of #GValue representing the parameters to
135 * @method. (It is *not* a #GValueArray, although if you have a
136 * #GValueArray, you can just pass its %values and %n_values fields.)
138 * The correspondence between glib types and XML-RPC types is:
140 * int: #int (%G_TYPE_INT)
141 * boolean: #gboolean (%G_TYPE_BOOLEAN)
142 * string: #char* (%G_TYPE_STRING)
143 * double: #double (%G_TYPE_DOUBLE)
144 * datetime.iso8601: #SoupDate (%SOUP_TYPE_DATE)
145 * base64: #GByteArray (%SOUP_TYPE_BYTE_ARRAY)
146 * struct: #GHashTable (%G_TYPE_HASH_TABLE)
147 * array: #GValueArray (%G_TYPE_VALUE_ARRAY)
149 * For structs, use a #GHashTable that maps strings to #GValue;
150 * soup_value_hash_new() and related methods can help with this.
152 * Return value: the text of the methodCall, or %NULL on error
155 soup_xmlrpc_build_method_call (const char *method_name,
156 GValue *params, int n_params)
159 xmlNode *node, *param;
164 doc = xmlNewDoc ((const xmlChar *)"1.0");
165 doc->standalone = FALSE;
166 doc->encoding = xmlCharStrdup ("UTF-8");
168 node = xmlNewDocNode (doc, NULL, (const xmlChar *)"methodCall", NULL);
169 xmlDocSetRootElement (doc, node);
170 xmlNewChild (node, NULL, (const xmlChar *)"methodName",
171 (const xmlChar *)method_name);
173 node = xmlNewChild (node, NULL, (const xmlChar *)"params", NULL);
174 for (i = 0; i < n_params; i++) {
175 param = xmlNewChild (node, NULL,
176 (const xmlChar *)"param", NULL);
177 if (!insert_value (param, ¶ms[i])) {
183 xmlDocDumpMemory (doc, &xmlbody, &len);
184 body = g_strndup ((char *)xmlbody, len);
191 soup_xmlrpc_request_newv (const char *uri, const char *method_name, va_list args)
197 params = soup_value_array_from_args (args);
201 body = soup_xmlrpc_build_method_call (method_name, params->values,
203 g_value_array_free (params);
207 msg = soup_message_new ("POST", uri);
208 soup_message_set_request (msg, "text/xml", SOUP_MEMORY_TAKE,
209 body, strlen (body));
214 * soup_xmlrpc_request_new:
215 * @uri: URI of the XML-RPC service
216 * @method_name: the name of the XML-RPC method to invoke at @uri
217 * @...: parameters for @method
219 * Creates an XML-RPC methodCall and returns a #SoupMessage, ready
220 * to send, for that method call.
222 * The parameters are passed as type/value pairs; ie, first a #GType,
223 * and then a value of the appropriate type, finally terminated by
226 * Return value: (transfer full): a #SoupMessage encoding the
227 * indicated XML-RPC request.
230 soup_xmlrpc_request_new (const char *uri, const char *method_name, ...)
235 va_start (args, method_name);
236 msg = soup_xmlrpc_request_newv (uri, method_name, args);
242 * soup_xmlrpc_build_method_response:
243 * @value: the return value
245 * This creates a (successful) XML-RPC methodResponse and returns it
246 * as a string. To create a fault response, use
247 * soup_xmlrpc_build_fault().
249 * The glib type to XML-RPC type mapping is as with
250 * soup_xmlrpc_build_method_call(), qv.
252 * Return value: the text of the methodResponse, or %NULL on error
255 soup_xmlrpc_build_method_response (GValue *value)
263 doc = xmlNewDoc ((const xmlChar *)"1.0");
264 doc->standalone = FALSE;
265 doc->encoding = xmlCharStrdup ("UTF-8");
267 node = xmlNewDocNode (doc, NULL,
268 (const xmlChar *)"methodResponse", NULL);
269 xmlDocSetRootElement (doc, node);
271 node = xmlNewChild (node, NULL, (const xmlChar *)"params", NULL);
272 node = xmlNewChild (node, NULL, (const xmlChar *)"param", NULL);
273 if (!insert_value (node, value)) {
278 xmlDocDumpMemory (doc, &xmlbody, &len);
279 body = g_strndup ((char *)xmlbody, len);
286 soup_xmlrpc_build_faultv (int fault_code, const char *fault_format, va_list args)
289 xmlNode *node, *member;
292 char *fault_string, *body;
295 fault_string = g_strdup_vprintf (fault_format, args);
297 doc = xmlNewDoc ((const xmlChar *)"1.0");
298 doc->standalone = FALSE;
299 doc->encoding = xmlCharStrdup ("UTF-8");
301 node = xmlNewDocNode (doc, NULL,
302 (const xmlChar *)"methodResponse", NULL);
303 xmlDocSetRootElement (doc, node);
304 node = xmlNewChild (node, NULL, (const xmlChar *)"fault", NULL);
305 node = xmlNewChild (node, NULL, (const xmlChar *)"value", NULL);
306 node = xmlNewChild (node, NULL, (const xmlChar *)"struct", NULL);
308 memset (&value, 0, sizeof (value));
310 member = xmlNewChild (node, NULL, (const xmlChar *)"member", NULL);
311 xmlNewChild (member, NULL,
312 (const xmlChar *)"name", (const xmlChar *)"faultCode");
313 g_value_init (&value, G_TYPE_INT);
314 g_value_set_int (&value, fault_code);
315 insert_value (member, &value);
316 g_value_unset (&value);
318 member = xmlNewChild (node, NULL, (const xmlChar *)"member", NULL);
319 xmlNewChild (member, NULL,
320 (const xmlChar *)"name", (const xmlChar *)"faultString");
321 g_value_init (&value, G_TYPE_STRING);
322 g_value_take_string (&value, fault_string);
323 insert_value (member, &value);
324 g_value_unset (&value);
326 xmlDocDumpMemory (doc, &xmlbody, &len);
327 body = g_strndup ((char *)xmlbody, len);
335 * soup_xmlrpc_build_fault:
336 * @fault_code: the fault code
337 * @fault_format: a printf()-style format string
338 * @...: the parameters to @fault_format
340 * This creates an XML-RPC fault response and returns it as a string.
341 * (To create a successful response, use
342 * soup_xmlrpc_build_method_response().)
344 * Return value: the text of the fault
347 soup_xmlrpc_build_fault (int fault_code, const char *fault_format, ...)
352 va_start (args, fault_format);
353 body = soup_xmlrpc_build_faultv (fault_code, fault_format, args);
359 * soup_xmlrpc_set_response:
360 * @msg: an XML-RPC request
361 * @type: the type of the response value
362 * @...: the response value
364 * Sets the status code and response body of @msg to indicate a
365 * successful XML-RPC call, with a return value given by @type and the
366 * following varargs argument, of the type indicated by @type.
369 soup_xmlrpc_set_response (SoupMessage *msg, GType type, ...)
375 va_start (args, type);
376 SOUP_VALUE_SETV (&value, type, args);
379 body = soup_xmlrpc_build_method_response (&value);
380 g_value_unset (&value);
381 soup_message_set_status (msg, SOUP_STATUS_OK);
382 soup_message_set_response (msg, "text/xml", SOUP_MEMORY_TAKE,
383 body, strlen (body));
387 * soup_xmlrpc_set_fault:
388 * @msg: an XML-RPC request
389 * @fault_code: the fault code
390 * @fault_format: a printf()-style format string
391 * @...: the parameters to @fault_format
393 * Sets the status code and response body of @msg to indicate an
394 * unsuccessful XML-RPC call, with the error described by @fault_code
398 soup_xmlrpc_set_fault (SoupMessage *msg, int fault_code,
399 const char *fault_format, ...)
404 va_start (args, fault_format);
405 body = soup_xmlrpc_build_faultv (fault_code, fault_format, args);
408 soup_message_set_status (msg, SOUP_STATUS_OK);
409 soup_message_set_response (msg, "text/xml", SOUP_MEMORY_TAKE,
410 body, strlen (body));
416 parse_value (xmlNode *xmlvalue, GValue *value)
419 const char *typename;
422 memset (value, 0, sizeof (GValue));
424 typenode = find_real_node (xmlvalue->children);
426 /* If no type node, it's a string */
427 content = xmlNodeGetContent (typenode);
428 g_value_init (value, G_TYPE_STRING);
429 g_value_set_string (value, (char *)content);
434 typename = (const char *)typenode->name;
436 if (!strcmp (typename, "i4") || !strcmp (typename, "int")) {
437 content = xmlNodeGetContent (typenode);
438 g_value_init (value, G_TYPE_INT);
439 g_value_set_int (value, atoi ((char *)content));
441 } else if (!strcmp (typename, "boolean")) {
442 content = xmlNodeGetContent (typenode);
443 g_value_init (value, G_TYPE_BOOLEAN);
444 g_value_set_boolean (value, atoi ((char *)content));
446 } else if (!strcmp (typename, "string")) {
447 content = xmlNodeGetContent (typenode);
448 g_value_init (value, G_TYPE_STRING);
449 g_value_set_string (value, (char *)content);
451 } else if (!strcmp (typename, "double")) {
452 content = xmlNodeGetContent (typenode);
453 g_value_init (value, G_TYPE_DOUBLE);
454 g_value_set_double (value, g_ascii_strtod ((char *)content, NULL));
456 } else if (!strcmp (typename, "dateTime.iso8601")) {
457 content = xmlNodeGetContent (typenode);
458 g_value_init (value, SOUP_TYPE_DATE);
459 g_value_take_boxed (value, soup_date_new_from_string ((char *)content));
461 } else if (!strcmp (typename, "base64")) {
466 content = xmlNodeGetContent (typenode);
467 decoded = g_base64_decode ((char *)content, &len);
468 ba = g_byte_array_sized_new (len);
469 g_byte_array_append (ba, decoded, len);
472 g_value_init (value, SOUP_TYPE_BYTE_ARRAY);
473 g_value_take_boxed (value, ba);
474 } else if (!strcmp (typename, "struct")) {
475 xmlNode *member, *child, *mname, *mxval;
479 hash = soup_value_hash_new ();
480 for (member = find_real_node (typenode->children);
482 member = find_real_node (member->next)) {
483 if (strcmp ((const char *)member->name, "member") != 0) {
484 g_hash_table_destroy (hash);
487 mname = mxval = NULL;
488 memset (&mgval, 0, sizeof (mgval));
490 for (child = find_real_node (member->children);
492 child = find_real_node (child->next)) {
493 if (!strcmp ((const char *)child->name, "name"))
495 else if (!strcmp ((const char *)child->name, "value"))
501 if (!mname || !mxval || !parse_value (mxval, &mgval)) {
502 g_hash_table_destroy (hash);
506 content = xmlNodeGetContent (mname);
507 soup_value_hash_insert_value (hash, (char *)content, &mgval);
509 g_value_unset (&mgval);
511 g_value_init (value, G_TYPE_HASH_TABLE);
512 g_value_take_boxed (value, hash);
513 } else if (!strcmp (typename, "array")) {
514 xmlNode *data, *xval;
518 data = find_real_node (typenode->children);
519 if (!data || strcmp ((const char *)data->name, "data") != 0)
522 array = g_value_array_new (1);
523 for (xval = find_real_node (data->children);
525 xval = find_real_node (xval->next)) {
526 memset (&gval, 0, sizeof (gval));
527 if (strcmp ((const char *)xval->name, "value") != 0 ||
528 !parse_value (xval, &gval)) {
529 g_value_array_free (array);
533 g_value_array_append (array, &gval);
534 g_value_unset (&gval);
536 g_value_init (value, G_TYPE_VALUE_ARRAY);
537 g_value_take_boxed (value, array);
545 * soup_xmlrpc_parse_method_call:
546 * @method_call: the XML-RPC methodCall string
547 * @length: the length of @method_call, or -1 if it is NUL-terminated
548 * @method_name: (out): on return, the methodName from @method_call
549 * @params: (out): on return, the parameters from @method_call
551 * Parses @method_call to get the name and parameters, and returns the
552 * parameter values in a #GValueArray; see also
553 * soup_xmlrpc_extract_method_call(), which is more convenient if you
554 * know in advance what the types of the parameters will be.
556 * Return value: success or failure.
559 soup_xmlrpc_parse_method_call (const char *method_call, int length,
560 char **method_name, GValueArray **params)
563 xmlNode *node, *param, *xval;
564 xmlChar *xmlMethodName = NULL;
565 gboolean success = FALSE;
568 doc = xmlParseMemory (method_call,
569 length == -1 ? strlen (method_call) : length);
573 node = xmlDocGetRootElement (doc);
574 if (!node || strcmp ((const char *)node->name, "methodCall") != 0)
577 node = find_real_node (node->children);
578 if (!node || strcmp ((const char *)node->name, "methodName") != 0)
580 xmlMethodName = xmlNodeGetContent (node);
582 node = find_real_node (node->next);
583 if (!node || strcmp ((const char *)node->name, "params") != 0)
586 *params = g_value_array_new (1);
587 param = find_real_node (node->children);
588 while (param && !strcmp ((const char *)param->name, "param")) {
589 xval = find_real_node (param->children);
590 if (!xval || strcmp ((const char *)xval->name, "value") != 0 ||
591 !parse_value (xval, &value)) {
592 g_value_array_free (*params);
595 g_value_array_append (*params, &value);
596 g_value_unset (&value);
598 param = find_real_node (param->next);
602 *method_name = g_strdup ((char *)xmlMethodName);
607 xmlFree (xmlMethodName);
612 * soup_xmlrpc_extract_method_call:
613 * @method_call: the XML-RPC methodCall string
614 * @length: the length of @method_call, or -1 if it is NUL-terminated
615 * @method_name: (out): on return, the methodName from @method_call
616 * @...: return types and locations for parameters
618 * Parses @method_call to get the name and parameters, and puts
619 * the parameters into variables of the appropriate types.
621 * The parameters are handled similarly to
622 * @soup_xmlrpc_build_method_call, with pairs of types and values,
623 * terminated by %G_TYPE_INVALID, except that values are pointers to
624 * variables of the indicated type, rather than values of the type.
626 * See also soup_xmlrpc_parse_method_call(), which can be used if
627 * you don't know the types of the parameters.
629 * Return value: success or failure.
632 soup_xmlrpc_extract_method_call (const char *method_call, int length,
633 char **method_name, ...)
639 if (!soup_xmlrpc_parse_method_call (method_call, length,
640 method_name, ¶ms))
643 va_start (args, method_name);
644 success = soup_value_array_to_args (params, args);
647 g_value_array_free (params);
652 * soup_xmlrpc_parse_method_response:
653 * @method_response: the XML-RPC methodResponse string
654 * @length: the length of @method_response, or -1 if it is NUL-terminated
655 * @value: (out): on return, the return value from @method_call
656 * @error: error return value
658 * Parses @method_response and returns the return value in @value. If
659 * @method_response is a fault, @value will be unchanged, and @error
660 * will be set to an error of type %SOUP_XMLRPC_FAULT, with the error
661 * #code containing the fault code, and the error #message containing
662 * the fault string. (If @method_response cannot be parsed at all,
663 * soup_xmlrpc_parse_method_response() will return %FALSE, but @error
666 * Return value: %TRUE if a return value was parsed, %FALSE if the
667 * response could not be parsed, or contained a fault.
670 soup_xmlrpc_parse_method_response (const char *method_response, int length,
671 GValue *value, GError **error)
675 gboolean success = FALSE;
677 doc = xmlParseMemory (method_response,
678 length == -1 ? strlen (method_response) : length);
682 node = xmlDocGetRootElement (doc);
683 if (!node || strcmp ((const char *)node->name, "methodResponse") != 0)
686 node = find_real_node (node->children);
690 if (!strcmp ((const char *)node->name, "fault") && error) {
694 GHashTable *fault_hash;
696 node = find_real_node (node->children);
697 if (!node || strcmp ((const char *)node->name, "value") != 0)
699 if (!parse_value (node, &fault_val))
701 if (!G_VALUE_HOLDS (&fault_val, G_TYPE_HASH_TABLE)) {
702 g_value_unset (&fault_val);
705 fault_hash = g_value_get_boxed (&fault_val);
706 if (!soup_value_hash_lookup (fault_hash, "faultCode",
707 G_TYPE_INT, &fault_code) ||
708 !soup_value_hash_lookup (fault_hash, "faultString",
709 G_TYPE_STRING, &fault_string)) {
710 g_value_unset (&fault_val);
714 g_set_error (error, SOUP_XMLRPC_FAULT,
715 fault_code, "%s", fault_string);
716 g_value_unset (&fault_val);
717 } else if (!strcmp ((const char *)node->name, "params")) {
718 node = find_real_node (node->children);
719 if (!node || strcmp ((const char *)node->name, "param") != 0)
721 node = find_real_node (node->children);
722 if (!node || strcmp ((const char *)node->name, "value") != 0)
724 if (!parse_value (node, value))
735 * soup_xmlrpc_extract_method_response:
736 * @method_response: the XML-RPC methodResponse string
737 * @length: the length of @method_response, or -1 if it is NUL-terminated
738 * @error: error return value
739 * @type: the expected type of the return value
740 * @...: location for return value
742 * Parses @method_response and extracts the return value into
743 * a variable of the correct type.
745 * If @method_response is a fault, the return value will be unset,
746 * and @error will be set to an error of type %SOUP_XMLRPC_FAULT, with
747 * the error #code containing the fault code, and the error #message
748 * containing the fault string. (If @method_response cannot be parsed
749 * at all, soup_xmlrpc_extract_method_response() will return %FALSE,
750 * but @error will be unset.)
752 * Return value: %TRUE if a return value was parsed, %FALSE if the
753 * response was of the wrong type, or contained a fault.
756 soup_xmlrpc_extract_method_response (const char *method_response, int length,
757 GError **error, GType type, ...)
762 if (!soup_xmlrpc_parse_method_response (method_response, length,
765 if (!G_VALUE_HOLDS (&value, type))
768 va_start (args, type);
769 SOUP_VALUE_GETV (&value, type, args);
777 soup_xmlrpc_error_quark (void)
781 error = g_quark_from_static_string ("soup_xmlrpc_error_quark");
786 soup_xmlrpc_fault_quark (void)
790 error = g_quark_from_static_string ("soup_xmlrpc_fault_quark");
795 find_real_node (xmlNode *node)
797 while (node && (node->type == XML_COMMENT_NODE ||
798 xmlIsBlankNode (node)))