/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * soup-xmlrpc.c: XML-RPC parser/generator * * Copyright (C) 2007 Red Hat, Inc. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "soup-xmlrpc-old.h" #include "soup.h" /** * SECTION:soup-xmlrpc * @short_description: XML-RPC support * **/ /* This whole file is deprecated and replaced by soup-xmlrpc.c */ G_GNUC_BEGIN_IGNORE_DEPRECATIONS static xmlNode *find_real_node (xmlNode *node); static gboolean insert_value (xmlNode *parent, GValue *value); static gboolean insert_value (xmlNode *parent, GValue *value) { GType type = G_VALUE_TYPE (value); xmlNode *xvalue; char buf[128]; xvalue = xmlNewChild (parent, NULL, (const xmlChar *)"value", NULL); if (type == G_TYPE_INT) { g_snprintf (buf, sizeof (buf), "%d", g_value_get_int (value)); xmlNewChild (xvalue, NULL, (const xmlChar *)"int", (const xmlChar *)buf); } else if (type == G_TYPE_BOOLEAN) { g_snprintf (buf, sizeof (buf), "%d", g_value_get_boolean (value)); xmlNewChild (xvalue, NULL, (const xmlChar *)"boolean", (const xmlChar *)buf); } else if (type == G_TYPE_STRING) { xmlNewTextChild (xvalue, NULL, (const xmlChar *)"string", (const xmlChar *)g_value_get_string (value)); } else if (type == G_TYPE_DOUBLE) { g_ascii_dtostr (buf, sizeof (buf), g_value_get_double (value)); xmlNewChild (xvalue, NULL, (const xmlChar *)"double", (const xmlChar *)buf); } else if (type == SOUP_TYPE_DATE) { SoupDate *date = g_value_get_boxed (value); char *timestamp = soup_date_to_string (date, SOUP_DATE_ISO8601_XMLRPC); xmlNewChild (xvalue, NULL, (const xmlChar *)"dateTime.iso8601", (const xmlChar *)timestamp); g_free (timestamp); } else if (type == SOUP_TYPE_BYTE_ARRAY) { GByteArray *ba = g_value_get_boxed (value); char *encoded; encoded = g_base64_encode (ba->data, ba->len); xmlNewChild (xvalue, NULL, (const xmlChar *)"base64", (const xmlChar *)encoded); g_free (encoded); } else if (type == G_TYPE_HASH_TABLE) { GHashTable *hash = g_value_get_boxed (value); GHashTableIter iter; gpointer mname, mvalue; xmlNode *struct_node, *member; struct_node = xmlNewChild (xvalue, NULL, (const xmlChar *)"struct", NULL); g_hash_table_iter_init (&iter, hash); while (g_hash_table_iter_next (&iter, &mname, &mvalue)) { member = xmlNewChild (struct_node, NULL, (const xmlChar *)"member", NULL); xmlNewTextChild (member, NULL, (const xmlChar *)"name", (const xmlChar *)mname); if (!insert_value (member, mvalue)) { xmlFreeNode (struct_node); struct_node = NULL; break; } } if (!struct_node) return FALSE; } else if (type == G_TYPE_VALUE_ARRAY) { GValueArray *va = g_value_get_boxed (value); xmlNode *node; guint i; node = xmlNewChild (xvalue, NULL, (const xmlChar *)"array", NULL); node = xmlNewChild (node, NULL, (const xmlChar *)"data", NULL); for (i = 0; i < va->n_values; i++) { if (!insert_value (node, &va->values[i])) return FALSE; } } else return FALSE; return TRUE; } /** * soup_xmlrpc_build_method_call: * @method_name: the name of the XML-RPC method * @params: (array length=n_params): arguments to @method * @n_params: length of @params * * This creates an XML-RPC methodCall and returns it as a string. * This is the low-level method that soup_xmlrpc_request_new() is * built on. * * @params is an array of #GValue representing the parameters to * @method. (It is *not* a #GValueArray, although if you have a * #GValueArray, you can just pass its valuesf and * n_values fields.) * * The correspondence between glib types and XML-RPC types is: * * int: #int (%G_TYPE_INT) * boolean: #gboolean (%G_TYPE_BOOLEAN) * string: #char* (%G_TYPE_STRING) * double: #double (%G_TYPE_DOUBLE) * datetime.iso8601: #SoupDate (%SOUP_TYPE_DATE) * base64: #GByteArray (%SOUP_TYPE_BYTE_ARRAY) * struct: #GHashTable (%G_TYPE_HASH_TABLE) * array: #GValueArray (%G_TYPE_VALUE_ARRAY) * * For structs, use a #GHashTable that maps strings to #GValue; * soup_value_hash_new() and related methods can help with this. * * Return value: (nullable): the text of the methodCall, or %NULL on * error * * Deprecated: Use soup_xmlrpc_build_request() instead. **/ char * soup_xmlrpc_build_method_call (const char *method_name, GValue *params, int n_params) { xmlDoc *doc; xmlNode *node, *param; xmlChar *xmlbody; int i, len; char *body; doc = xmlNewDoc ((const xmlChar *)"1.0"); doc->standalone = FALSE; doc->encoding = xmlCharStrdup ("UTF-8"); node = xmlNewDocNode (doc, NULL, (const xmlChar *)"methodCall", NULL); xmlDocSetRootElement (doc, node); xmlNewChild (node, NULL, (const xmlChar *)"methodName", (const xmlChar *)method_name); node = xmlNewChild (node, NULL, (const xmlChar *)"params", NULL); for (i = 0; i < n_params; i++) { param = xmlNewChild (node, NULL, (const xmlChar *)"param", NULL); if (!insert_value (param, ¶ms[i])) { xmlFreeDoc (doc); return NULL; } } xmlDocDumpMemory (doc, &xmlbody, &len); body = g_strndup ((char *)xmlbody, len); xmlFree (xmlbody); xmlFreeDoc (doc); return body; } static SoupMessage * soup_xmlrpc_request_newv (const char *uri, const char *method_name, va_list args) { SoupMessage *msg; GValueArray *params; char *body; params = soup_value_array_from_args (args); if (!params) return NULL; body = soup_xmlrpc_build_method_call (method_name, params->values, params->n_values); g_value_array_free (params); if (!body) return NULL; msg = soup_message_new ("POST", uri); soup_message_set_request (msg, "text/xml", SOUP_MEMORY_TAKE, body, strlen (body)); return msg; } /** * soup_xmlrpc_request_new: * @uri: URI of the XML-RPC service * @method_name: the name of the XML-RPC method to invoke at @uri * @...: parameters for @method * * Creates an XML-RPC methodCall and returns a #SoupMessage, ready * to send, for that method call. * * The parameters are passed as type/value pairs; ie, first a #GType, * and then a value of the appropriate type, finally terminated by * %G_TYPE_INVALID. * * Return value: (transfer full): a #SoupMessage encoding the * indicated XML-RPC request. * * Deprecated: Use soup_xmlrpc_message_new() instead. **/ SoupMessage * soup_xmlrpc_request_new (const char *uri, const char *method_name, ...) { SoupMessage *msg; va_list args; va_start (args, method_name); msg = soup_xmlrpc_request_newv (uri, method_name, args); va_end (args); return msg; } /** * soup_xmlrpc_build_method_response: * @value: the return value * * This creates a (successful) XML-RPC methodResponse and returns it * as a string. To create a fault response, use * soup_xmlrpc_build_fault(). * * The glib type to XML-RPC type mapping is as with * soup_xmlrpc_build_method_call(), qv. * * Return value: (nullable): the text of the methodResponse, or %NULL * on error * * Deprecated: Use soup_xmlrpc_build_response() instead. **/ char * soup_xmlrpc_build_method_response (GValue *value) { xmlDoc *doc; xmlNode *node; xmlChar *xmlbody; char *body; int len; doc = xmlNewDoc ((const xmlChar *)"1.0"); doc->standalone = FALSE; doc->encoding = xmlCharStrdup ("UTF-8"); node = xmlNewDocNode (doc, NULL, (const xmlChar *)"methodResponse", NULL); xmlDocSetRootElement (doc, node); node = xmlNewChild (node, NULL, (const xmlChar *)"params", NULL); node = xmlNewChild (node, NULL, (const xmlChar *)"param", NULL); if (!insert_value (node, value)) { xmlFreeDoc (doc); return NULL; } xmlDocDumpMemory (doc, &xmlbody, &len); body = g_strndup ((char *)xmlbody, len); xmlFree (xmlbody); xmlFreeDoc (doc); return body; } /** * soup_xmlrpc_set_response: * @msg: an XML-RPC request * @type: the type of the response value * @...: the response value * * Sets the status code and response body of @msg to indicate a * successful XML-RPC call, with a return value given by @type and the * following varargs argument, of the type indicated by @type. * * Deprecated: Use soup_xmlrpc_message_set_response() instead. **/ void soup_xmlrpc_set_response (SoupMessage *msg, GType type, ...) { va_list args; GValue value; char *body; va_start (args, type); SOUP_VALUE_SETV (&value, type, args); va_end (args); body = soup_xmlrpc_build_method_response (&value); g_value_unset (&value); soup_message_set_status (msg, SOUP_STATUS_OK); soup_message_set_response (msg, "text/xml", SOUP_MEMORY_TAKE, body, strlen (body)); } 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 * @fault_code: the fault code * @fault_format: a printf()-style format string * @...: the parameters to @fault_format * * Sets the status code and response body of @msg to indicate an * unsuccessful XML-RPC call, with the error described by @fault_code * and @fault_format. * * Deprecated: Use soup_xmlrpc_message_set_fault() instead. **/ void soup_xmlrpc_set_fault (SoupMessage *msg, int fault_code, const char *fault_format, ...) { va_list args; char *body; va_start (args, fault_format); body = soup_xmlrpc_build_faultv (fault_code, fault_format, args); va_end (args); soup_message_set_status (msg, SOUP_STATUS_OK); soup_message_set_response (msg, "text/xml", SOUP_MEMORY_TAKE, body, strlen (body)); } static gboolean parse_value (xmlNode *xmlvalue, GValue *value) { xmlNode *typenode; const char *typename; xmlChar *content; memset (value, 0, sizeof (GValue)); typenode = find_real_node (xmlvalue->children); if (!typenode) { /* If no type node, it's a string */ content = xmlNodeGetContent (typenode); g_value_init (value, G_TYPE_STRING); g_value_set_string (value, (char *)content); xmlFree (content); return TRUE; } typename = (const char *)typenode->name; if (!strcmp (typename, "i4") || !strcmp (typename, "int")) { content = xmlNodeGetContent (typenode); g_value_init (value, G_TYPE_INT); g_value_set_int (value, atoi ((char *)content)); xmlFree (content); } else if (!strcmp (typename, "boolean")) { content = xmlNodeGetContent (typenode); g_value_init (value, G_TYPE_BOOLEAN); g_value_set_boolean (value, atoi ((char *)content)); xmlFree (content); } else if (!strcmp (typename, "string")) { content = xmlNodeGetContent (typenode); g_value_init (value, G_TYPE_STRING); g_value_set_string (value, (char *)content); xmlFree (content); } else if (!strcmp (typename, "double")) { content = xmlNodeGetContent (typenode); g_value_init (value, G_TYPE_DOUBLE); g_value_set_double (value, g_ascii_strtod ((char *)content, NULL)); xmlFree (content); } else if (!strcmp (typename, "dateTime.iso8601")) { content = xmlNodeGetContent (typenode); g_value_init (value, SOUP_TYPE_DATE); g_value_take_boxed (value, soup_date_new_from_string ((char *)content)); xmlFree (content); } else if (!strcmp (typename, "base64")) { GByteArray *ba; guchar *decoded; gsize len; content = xmlNodeGetContent (typenode); decoded = g_base64_decode ((char *)content, &len); ba = g_byte_array_sized_new (len); g_byte_array_append (ba, decoded, len); g_free (decoded); xmlFree (content); g_value_init (value, SOUP_TYPE_BYTE_ARRAY); g_value_take_boxed (value, ba); } else if (!strcmp (typename, "struct")) { xmlNode *member, *child, *mname, *mxval; GHashTable *hash; GValue mgval; hash = soup_value_hash_new (); for (member = find_real_node (typenode->children); member; member = find_real_node (member->next)) { if (strcmp ((const char *)member->name, "member") != 0) { g_hash_table_destroy (hash); return FALSE; } mname = mxval = NULL; memset (&mgval, 0, sizeof (mgval)); for (child = find_real_node (member->children); child; child = find_real_node (child->next)) { if (!strcmp ((const char *)child->name, "name")) mname = child; else if (!strcmp ((const char *)child->name, "value")) mxval = child; else break; } if (!mname || !mxval || !parse_value (mxval, &mgval)) { g_hash_table_destroy (hash); return FALSE; } content = xmlNodeGetContent (mname); soup_value_hash_insert_value (hash, (char *)content, &mgval); xmlFree (content); g_value_unset (&mgval); } g_value_init (value, G_TYPE_HASH_TABLE); g_value_take_boxed (value, hash); } else if (!strcmp (typename, "array")) { xmlNode *data, *xval; GValueArray *array; GValue gval; data = find_real_node (typenode->children); if (!data || strcmp ((const char *)data->name, "data") != 0) return FALSE; array = g_value_array_new (1); for (xval = find_real_node (data->children); xval; xval = find_real_node (xval->next)) { memset (&gval, 0, sizeof (gval)); if (strcmp ((const char *)xval->name, "value") != 0 || !parse_value (xval, &gval)) { g_value_array_free (array); return FALSE; } g_value_array_append (array, &gval); g_value_unset (&gval); } g_value_init (value, G_TYPE_VALUE_ARRAY); g_value_take_boxed (value, array); } else return FALSE; return TRUE; } /** * soup_xmlrpc_parse_method_call: * @method_call: the XML-RPC methodCall string * @length: the length of @method_call, or -1 if it is NUL-terminated * @method_name: (out): on return, the methodName from @method_call * @params: (out): on return, the parameters from @method_call * * Parses @method_call to get the name and parameters, and returns the * parameter values in a #GValueArray; see also * soup_xmlrpc_extract_method_call(), which is more convenient if you * know in advance what the types of the parameters will be. * * Return value: success or failure. * * Deprecated: Use soup_xmlrpc_parse_request_full() instead. **/ gboolean soup_xmlrpc_parse_method_call (const char *method_call, int length, char **method_name, GValueArray **params) { xmlDoc *doc; xmlNode *node, *param, *xval; xmlChar *xmlMethodName = NULL; gboolean success = FALSE; GValue value; doc = xmlParseMemory (method_call, length == -1 ? strlen (method_call) : length); if (!doc) return FALSE; node = xmlDocGetRootElement (doc); if (!node || strcmp ((const char *)node->name, "methodCall") != 0) goto fail; node = find_real_node (node->children); if (!node || strcmp ((const char *)node->name, "methodName") != 0) goto fail; xmlMethodName = xmlNodeGetContent (node); node = find_real_node (node->next); if (node) { if (strcmp ((const char *)node->name, "params") != 0) goto fail; *params = soup_value_array_new (); param = find_real_node (node->children); while (param && !strcmp ((const char *)param->name, "param")) { xval = find_real_node (param->children); if (!xval || strcmp ((const char *)xval->name, "value") != 0 || !parse_value (xval, &value)) { g_value_array_free (*params); goto fail; } g_value_array_append (*params, &value); g_value_unset (&value); param = find_real_node (param->next); } } else *params = soup_value_array_new (); success = TRUE; *method_name = g_strdup ((char *)xmlMethodName); fail: xmlFreeDoc (doc); if (xmlMethodName) xmlFree (xmlMethodName); return success; } /** * soup_xmlrpc_extract_method_call: * @method_call: the XML-RPC methodCall string * @length: the length of @method_call, or -1 if it is NUL-terminated * @method_name: (out): on return, the methodName from @method_call * @...: return types and locations for parameters * * Parses @method_call to get the name and parameters, and puts * the parameters into variables of the appropriate types. * * The parameters are handled similarly to * @soup_xmlrpc_build_method_call, with pairs of types and values, * terminated by %G_TYPE_INVALID, except that values are pointers to * variables of the indicated type, rather than values of the type. * * See also soup_xmlrpc_parse_method_call(), which can be used if * you don't know the types of the parameters. * * Return value: success or failure. * * Deprecated: Use soup_xmlrpc_parse_request_full() instead. **/ gboolean soup_xmlrpc_extract_method_call (const char *method_call, int length, char **method_name, ...) { GValueArray *params; gboolean success; va_list args; if (!soup_xmlrpc_parse_method_call (method_call, length, method_name, ¶ms)) return FALSE; va_start (args, method_name); success = soup_value_array_to_args (params, args); va_end (args); g_value_array_free (params); return success; } /** * soup_xmlrpc_parse_method_response: * @method_response: the XML-RPC methodResponse string * @length: the length of @method_response, or -1 if it is NUL-terminated * @value: (out): on return, the return value from @method_call * @error: error return value * * Parses @method_response and returns the return value in @value. If * @method_response is a fault, @value will be unchanged, and @error * will be set to an error of type %SOUP_XMLRPC_FAULT, with the error * #code containing the fault code, and the error #message containing * the fault string. (If @method_response cannot be parsed at all, * soup_xmlrpc_parse_method_response() will return %FALSE, but @error * will be unset.) * * Return value: %TRUE if a return value was parsed, %FALSE if the * response could not be parsed, or contained a fault. * * Deprecated: Use soup_xmlrpc_parse_response() instead. **/ gboolean soup_xmlrpc_parse_method_response (const char *method_response, int length, GValue *value, GError **error) { xmlDoc *doc; xmlNode *node; gboolean success = FALSE; doc = xmlParseMemory (method_response, length == -1 ? strlen (method_response) : length); if (!doc) return FALSE; node = xmlDocGetRootElement (doc); if (!node || strcmp ((const char *)node->name, "methodResponse") != 0) goto fail; node = find_real_node (node->children); if (!node) goto fail; if (!strcmp ((const char *)node->name, "fault") && error) { int fault_code; char *fault_string; GValue fault_val; GHashTable *fault_hash; node = find_real_node (node->children); if (!node || strcmp ((const char *)node->name, "value") != 0) goto fail; if (!parse_value (node, &fault_val)) goto fail; if (!G_VALUE_HOLDS (&fault_val, G_TYPE_HASH_TABLE)) { g_value_unset (&fault_val); goto fail; } fault_hash = g_value_get_boxed (&fault_val); if (!soup_value_hash_lookup (fault_hash, "faultCode", G_TYPE_INT, &fault_code) || !soup_value_hash_lookup (fault_hash, "faultString", G_TYPE_STRING, &fault_string)) { g_value_unset (&fault_val); goto fail; } g_set_error (error, SOUP_XMLRPC_FAULT, fault_code, "%s", fault_string); g_value_unset (&fault_val); } else if (!strcmp ((const char *)node->name, "params")) { node = find_real_node (node->children); if (!node || strcmp ((const char *)node->name, "param") != 0) goto fail; node = find_real_node (node->children); if (!node || strcmp ((const char *)node->name, "value") != 0) goto fail; if (!parse_value (node, value)) goto fail; success = TRUE; } fail: xmlFreeDoc (doc); return success; } /** * soup_xmlrpc_extract_method_response: * @method_response: the XML-RPC methodResponse string * @length: the length of @method_response, or -1 if it is NUL-terminated * @error: error return value * @type: the expected type of the return value * @...: location for return value * * Parses @method_response and extracts the return value into * a variable of the correct type. * * If @method_response is a fault, the return value will be unset, * and @error will be set to an error of type %SOUP_XMLRPC_FAULT, with * the error #code containing the fault code, and the error #message * containing the fault string. (If @method_response cannot be parsed * at all, soup_xmlrpc_extract_method_response() will return %FALSE, * but @error will be unset.) * * Return value: %TRUE if a return value was parsed, %FALSE if the * response was of the wrong type, or contained a fault. * * Deprecated: Use soup_xmlrpc_parse_response() instead. **/ gboolean soup_xmlrpc_extract_method_response (const char *method_response, int length, GError **error, GType type, ...) { GValue value; va_list args; if (!soup_xmlrpc_parse_method_response (method_response, length, &value, error)) return FALSE; if (!G_VALUE_HOLDS (&value, type)) return FALSE; va_start (args, type); SOUP_VALUE_GETV (&value, type, args); va_end (args); return TRUE; } static xmlNode * find_real_node (xmlNode *node) { while (node && (node->type == XML_COMMENT_NODE || xmlIsBlankNode (node))) node = node->next; return node; } G_GNUC_END_IGNORE_DEPRECATIONS