diff options
-rw-r--r-- | ChangeLog | 19 | ||||
-rw-r--r-- | TODO | 10 | ||||
-rw-r--r-- | configure.in | 5 | ||||
-rw-r--r-- | libsoup/Makefile.am | 10 | ||||
-rw-r--r-- | libsoup/soup-date.c | 261 | ||||
-rw-r--r-- | libsoup/soup-date.h | 19 | ||||
-rw-r--r-- | libsoup/soup-xmlrpc-message.c | 359 | ||||
-rw-r--r-- | libsoup/soup-xmlrpc-message.h | 75 | ||||
-rw-r--r-- | libsoup/soup-xmlrpc-response.c | 635 | ||||
-rw-r--r-- | libsoup/soup-xmlrpc-response.h | 87 | ||||
-rw-r--r-- | tests/.cvsignore | 2 | ||||
-rw-r--r-- | tests/Makefile.am | 4 | ||||
-rw-r--r-- | tests/date.c | 55 | ||||
-rw-r--r-- | tests/getbug.c | 137 |
14 files changed, 1670 insertions, 8 deletions
@@ -1,5 +1,24 @@ 2005-06-14 Dan Winship <danw@novell.com> + * configure.in: check for gmtime_r + + * libsoup/soup-date.c: date/time-manipulation functions + + * libsoup/soup-xmlrpc-message.c: + * libsoup/soup-xmlrpc-response.c: XMLRPC message classes, from + Mariano Suarez-Alvarez, Fernando Herrera, and Jeff Bailey. + [#300227] + + * tests/date.c: soup-date test code + + * tests/getbug.c: XMLRPC test code. (Should be switched to use + bugzilla.gnome.org once bgo supports XMLRPC.) + + * TODO: XMLRPC is implemented now (but shares the problem with + SOAP that the API is not very good). + +2005-06-14 Dan Winship <danw@novell.com> + * libsoup/*.[ch]: add/fix gtk-doc comments, make functions match prototypes, etc @@ -31,14 +31,12 @@ HTTP Features SOAP/XML-RPC Features --------------------- -* Add XML-RPC support (bug-buddy wants this) - * Bring back WSDL compiler from old soup module? -* The current SoupSoapMessage/Response API doesn't really make life a - whole lot easier for the user... You have to marshall the data in - exactly the right order anyway, so you might as well just be - g_string_appending... +* The current SOAP/XMLRPC API doesn't really make life a whole lot + easier for the user... You have to marshall the data in exactly the + right order anyway, so you might as well just be using + g_string_append... Misc Features diff --git a/configure.in b/configure.in index f2f51337..0fc53e56 100644 --- a/configure.in +++ b/configure.in @@ -100,6 +100,11 @@ esac AC_MSG_RESULT([$os_win32]) AM_CONDITIONAL(OS_WIN32, [test $os_win32 = yes]) +dnl ******************* +dnl *** Misc checks *** +dnl ******************* +AC_CHECK_FUNCS(gmtime_r) + dnl ********************************* dnl *** Networking library checks *** dnl ********************************* diff --git a/libsoup/Makefile.am b/libsoup/Makefile.am index 8e9b1609..f1045c71 100644 --- a/libsoup/Makefile.am +++ b/libsoup/Makefile.am @@ -35,6 +35,7 @@ libsoupinclude_HEADERS = \ soup.h \ soup-address.h \ soup-connection.h \ + soup-date.h \ soup-headers.h \ soup-message.h \ soup-message-filter.h \ @@ -53,7 +54,9 @@ libsoupinclude_HEADERS = \ soup-socket.h \ soup-status.h \ soup-types.h \ - soup-uri.h + soup-uri.h \ + soup-xmlrpc-message.h \ + soup-xmlrpc-response.h lib_LTLIBRARIES = libsoup-2.4.la @@ -79,6 +82,7 @@ libsoup_2_4_la_SOURCES = \ soup-connection.c \ soup-connection-ntlm.h \ soup-connection-ntlm.c \ + soup-date.c \ soup-dns.h \ soup-dns.c \ soup-gnutls.c \ @@ -107,6 +111,8 @@ libsoup_2_4_la_SOURCES = \ soup-socket.c \ soup-ssl.h \ soup-status.c \ - soup-uri.c + soup-uri.c \ + soup-xmlrpc-message.c \ + soup-xmlrpc-response.c EXTRA_DIST= soup-marshal.list diff --git a/libsoup/soup-date.c b/libsoup/soup-date.c new file mode 100644 index 00000000..b2c49584 --- /dev/null +++ b/libsoup/soup-date.c @@ -0,0 +1,261 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * soup-date.c: Date/time functions + * + * Copyright (C) 2005, Novell, Inc. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <string.h> +#include <glib.h> + +#include "soup-date.h" + +/* Do not internationalize */ +static const char *months[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +/* Do not internationalize */ +static const char *days[] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" +}; + +static const int days_before[] = { + 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 +}; + +static int +parse_month (const char *month) +{ + int i; + + for (i = 0; i < G_N_ELEMENTS (months); i++) { + if (!strncmp (month, months[i], 3)) + return i; + } + return -1; +} + +/** + * soup_mktime_utc: + * @tm: the UTC time + * + * Converts @tm to a #time_t. Unlike with mktime(), @tm is interpreted + * as being a UTC time. + * + * Return value: @tm as a #time_t + **/ +time_t +soup_mktime_utc (struct tm *tm) +{ + time_t tt; + + /* We check the month because (a) if we don't, the + * days_before[] part below may access random memory, and (b) + * soup_date_parse() doesn't check the return value of + * parse_month(). The caller is responsible for ensure the + * sanity of everything else. + */ + if (tm->tm_mon < 0 || tm->tm_mon > 11) + return (time_t)-1; + + tt = (tm->tm_year - 70) * 365; + tt += (tm->tm_year - 68) / 4; + tt += days_before[tm->tm_mon] + tm->tm_mday - 1; + if (tm->tm_year % 4 == 2 && tm->tm_mon < 2) + tt--; + tt = ((((tt * 24) + tm->tm_hour) * 60) + tm->tm_min) * 60 + tm->tm_sec; + return tt; +} + +/** + * soup_gmtime: + * @when: a #time_t + * @tm: a struct tm to be filled in with the expansion of @when + * + * Expands @when into @tm (as a UTC time). This is just a wrapper + * around gmtime_r() (or gmtime() on lame platforms). + **/ +void +soup_gmtime (const time_t *when, struct tm *tm) +{ +#ifdef HAVE_GMTIME_R + gmtime_r (when, tm); +#else + *tm = *gmtime (when); +#endif +} + +/** + * soup_date_parse: + * @timestamp: a timestamp, in any of the allowed HTTP 1.1 formats + * + * Parses @timestamp and returns its value as a #time_t. + * + * Return value: the #time_t corresponding to @timestamp, or -1 if + * @timestamp couldn't be parsed. + **/ +time_t +soup_date_parse (const char *timestamp) +{ + struct tm tm; + int len = strlen (timestamp); + + if (len < 4) + return (time_t)-1; + + switch (timestamp[3]) { + case ',': + /* rfc1123-date = wkday "," SP date1 SP time SP "GMT" + * date1 = 2DIGIT SP month SP 4DIGIT + * time = 2DIGIT ":" 2DIGIT ":" 2DIGIT + * + * eg, "Sun, 06 Nov 1994 08:49:37 GMT" + */ + if (len != 29 || strcmp (timestamp + 25, " GMT") != 0) + return (time_t)-1; + + tm.tm_mday = atoi (timestamp + 5); + tm.tm_mon = parse_month (timestamp + 8); + tm.tm_year = atoi (timestamp + 12) - 1900; + tm.tm_hour = atoi (timestamp + 17); + tm.tm_min = atoi (timestamp + 20); + tm.tm_sec = atoi (timestamp + 23); + break; + + case ' ': + /* asctime-date = wkday SP date3 SP time SP 4DIGIT + * date3 = month SP ( 2DIGIT | ( SP 1DIGIT )) + * time = 2DIGIT ":" 2DIGIT ":" 2DIGIT + * + * eg, "Sun Nov 6 08:49:37 1994" + */ + if (len != 24) + return (time_t)-1; + + tm.tm_mon = parse_month (timestamp + 4); + tm.tm_mday = atoi (timestamp + 8); + tm.tm_hour = atoi (timestamp + 11); + tm.tm_min = atoi (timestamp + 14); + tm.tm_sec = atoi (timestamp + 17); + tm.tm_year = atoi (timestamp + 20) - 1900; + break; + + default: + /* rfc850-date = weekday "," SP date2 SP time SP "GMT" + * date2 = 2DIGIT "-" month "-" 2DIGIT + * time = 2DIGIT ":" 2DIGIT ":" 2DIGIT + * + * eg, "Sunday, 06-Nov-94 08:49:37 GMT" + */ + timestamp = strchr (timestamp, ','); + if (timestamp == NULL || strlen (timestamp) != 24 || strcmp (timestamp + 20, " GMT") != 0) + return (time_t)-1; + + tm.tm_mday = atoi (timestamp + 2); + tm.tm_mon = parse_month (timestamp + 5); + tm.tm_year = atoi (timestamp + 9); + tm.tm_hour = atoi (timestamp + 12); + tm.tm_min = atoi (timestamp + 15); + tm.tm_sec = atoi (timestamp + 18); + break; + } + + return soup_mktime_utc (&tm); +} + +/** + * soup_date_generate: + * @when: the time to generate a timestamp for + * + * Generates an HTTP 1.1 Date header corresponding to @when. + * + * Return value: the timestamp, which the caller must free. + **/ +char * +soup_date_generate (time_t when) +{ + struct tm tm; + gmtime_r (&when, &tm); + + /* RFC1123 format, eg, "Sun, 06 Nov 1994 08:49:37 GMT" */ + return g_strdup_printf ("%s, %02d %s %04d %02d:%02d:%02d GMT", + days[tm.tm_wday], tm.tm_mday, + months[tm.tm_mon], tm.tm_year + 1900, + tm.tm_hour, tm.tm_min, tm.tm_sec); +} + +/** + * soup_date_iso8601_parse: + * @timestamp: an ISO8601 timestamp + * + * Converts @timestamp to a %time_t value. @timestamp can be in any of the + * iso8601 formats that specify both a date and a time. + * + * Return value: the %time_t corresponding to @timestamp, or -1 on error. + **/ +time_t +soup_date_iso8601_parse (const char *timestamp) +{ + struct tm tm; + long val; + time_t tt; + + val = strtoul (timestamp, (char **)×tamp, 10); + if (*timestamp == '-') { + // YYYY-MM-DD + tm.tm_year = val - 1900; + timestamp++; + tm.tm_mon = strtoul (timestamp, (char **)×tamp, 10) - 1; + if (*timestamp++ != '-') + return -1; + tm.tm_mday = strtoul (timestamp, (char **)×tamp, 10); + } else { + // YYYYMMDD + tm.tm_mday = val % 100; + tm.tm_mon = (val % 10000) / 100; + tm.tm_year = val / 10000; + } + + if (*timestamp++ != 'T') + return -1; + + val = strtoul (timestamp, (char **)×tamp, 10); + if (*timestamp == ':') { + // hh:mm:ss + tm.tm_hour = val; + timestamp++; + tm.tm_min = strtoul (timestamp, (char **)×tamp, 10); + if (*timestamp++ != ':') + return -1; + tm.tm_sec = strtoul (timestamp, (char **)×tamp, 10); + } else { + // hhmmss + tm.tm_sec = val % 100; + tm.tm_min = (val % 10000) / 100; + tm.tm_hour = val / 10000; + } + + tt = soup_mktime_utc (&tm); + + if (*timestamp == '.') + strtoul (timestamp + 1, (char **)×tamp, 10); + + if (*timestamp == '+' || *timestamp == '-') { + int sign = (*timestamp == '+') ? -1 : 1; + val = strtoul (timestamp + 1, (char **)×tamp, 10); + if (*timestamp == ':') + val = 60 * val + strtoul (timestamp + 1, NULL, 10); + else + val = 60 * (val / 100) + (val % 100); + tt += sign * val; + } + + return tt; +} diff --git a/libsoup/soup-date.h b/libsoup/soup-date.h new file mode 100644 index 00000000..c3a60be6 --- /dev/null +++ b/libsoup/soup-date.h @@ -0,0 +1,19 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2005 Novell, Inc. + */ + +#ifndef SOUP_DATE_H +#define SOUP_DATE_H 1 + +#include <time.h> + +time_t soup_mktime_utc (struct tm *tm); +void soup_gmtime (const time_t *when, struct tm *tm); + +time_t soup_date_parse (const char *when); +char *soup_date_generate (time_t when); + +time_t soup_date_iso8601_parse (const char *when); + +#endif /* SOUP_DATE_H */ diff --git a/libsoup/soup-xmlrpc-message.c b/libsoup/soup-xmlrpc-message.c new file mode 100644 index 00000000..55548fc2 --- /dev/null +++ b/libsoup/soup-xmlrpc-message.c @@ -0,0 +1,359 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * soup-xmlrpc-message.c: XMLRPC request message + * + * Copyright (C) 2003, Novell, Inc. + * Copyright (C) 2004, Mariano Suarez-Alvarez <mariano@gnome.org> + * Copyright (C) 2004, Fernando Herrera <fherrera@onirica.com> + * Copyright (C) 2005, Jeff Bailey <jbailey@ubuntu.com> + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <time.h> + +#include "soup-date.h" +#include "soup-misc.h" +#include "soup-xmlrpc-message.h" + +G_DEFINE_TYPE (SoupXmlrpcMessage, soup_xmlrpc_message, SOUP_TYPE_MESSAGE) + +typedef struct { + xmlDocPtr doc; + xmlNodePtr last_node; +} SoupXmlrpcMessagePrivate; +#define SOUP_XMLRPC_MESSAGE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_XMLRPC_MESSAGE, SoupXmlrpcMessagePrivate)) + +static void soup_xmlrpc_message_end_element (SoupXmlrpcMessage *msg); + +static void +finalize (GObject *object) +{ + SoupXmlrpcMessagePrivate *priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (object); + + if (priv->doc) + xmlFreeDoc (priv->doc); + + G_OBJECT_CLASS (soup_xmlrpc_message_parent_class)->finalize (object); +} + +static void +soup_xmlrpc_message_class_init (SoupXmlrpcMessageClass *soup_xmlrpc_message_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (soup_xmlrpc_message_class); + + g_type_class_add_private (soup_xmlrpc_message_class, sizeof (SoupXmlrpcMessagePrivate)); + + object_class->finalize = finalize; +} + +static void +soup_xmlrpc_message_init (SoupXmlrpcMessage *msg) +{ + SoupXmlrpcMessagePrivate *priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (msg); + + xmlKeepBlanksDefault (0); + + priv->doc = xmlNewDoc ("1.0"); + priv->doc->standalone = FALSE; + priv->doc->encoding = g_strdup ("UTF-8"); +} + + +SoupXmlrpcMessage * +soup_xmlrpc_message_new (const char *uri_string) +{ + SoupXmlrpcMessage *msg; + SoupUri *uri; + + uri = soup_uri_new (uri_string); + if (!uri) + return NULL; + + msg = soup_xmlrpc_message_new_from_uri (uri); + + soup_uri_free (uri); + + return msg; +} + +SoupXmlrpcMessage * +soup_xmlrpc_message_new_from_uri (const SoupUri *uri) +{ + SoupXmlrpcMessage *msg; + + msg = g_object_new (SOUP_TYPE_XMLRPC_MESSAGE, NULL); + SOUP_MESSAGE (msg)->method = SOUP_METHOD_POST; + soup_message_set_uri (SOUP_MESSAGE (msg), uri); + + return msg; +} + +void +soup_xmlrpc_message_start_call (SoupXmlrpcMessage *msg, const char *method_name) +{ + SoupXmlrpcMessagePrivate *priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (msg); + xmlNodePtr root; + + g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); + + root = xmlNewDocNode (priv->doc, NULL, "methodCall", NULL); + xmlDocSetRootElement (priv->doc, root); + + xmlNewChild (root, NULL, "methodName", method_name); + + priv->last_node = root; + + priv->last_node = xmlNewChild (priv->last_node, NULL, "params", NULL); +} + +void +soup_xmlrpc_message_end_call (SoupXmlrpcMessage *msg) +{ + g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); + + soup_xmlrpc_message_end_element (msg); + soup_xmlrpc_message_end_element (msg); + soup_xmlrpc_message_end_element (msg); +} + +void +soup_xmlrpc_message_start_param (SoupXmlrpcMessage *msg) +{ + SoupXmlrpcMessagePrivate *priv; + + g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); + priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (msg); + + priv->last_node = xmlNewChild (priv->last_node, NULL, "param", NULL); +} + +void +soup_xmlrpc_message_end_param (SoupXmlrpcMessage *msg) +{ + g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); + + soup_xmlrpc_message_end_element (msg); +} + +void +soup_xmlrpc_message_write_int (SoupXmlrpcMessage *msg, long i) +{ + SoupXmlrpcMessagePrivate *priv; + char *str; + + g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); + priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (msg); + + str = g_strdup_printf ("%ld", i); + + priv->last_node = xmlNewChild (priv->last_node, NULL, "value", NULL); + xmlNewTextChild (priv->last_node, NULL, "i4", str); + soup_xmlrpc_message_end_element (msg); + + g_free (str); +} + +void +soup_xmlrpc_message_write_boolean (SoupXmlrpcMessage *msg, gboolean b) +{ + SoupXmlrpcMessagePrivate *priv; + + g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); + priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (msg); + + priv->last_node = xmlNewChild (priv->last_node, NULL, "value", NULL); + xmlNewChild (priv->last_node, NULL, "boolean", b ? "1" : "0"); + soup_xmlrpc_message_end_element (msg); +} + +void +soup_xmlrpc_message_write_string (SoupXmlrpcMessage *msg, const char *str) +{ + SoupXmlrpcMessagePrivate *priv; + + g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); + priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (msg); + + priv->last_node = xmlNewChild (priv->last_node, NULL, "value", NULL); + xmlNewTextChild (priv->last_node, NULL, "string", str); + soup_xmlrpc_message_end_element (msg); +} + +void +soup_xmlrpc_message_write_double (SoupXmlrpcMessage *msg, double d) +{ + SoupXmlrpcMessagePrivate *priv; + char *str; + + g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); + priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (msg); + + str = g_strdup_printf ("%f", d); + + priv->last_node = xmlNewChild (priv->last_node, NULL, "value", NULL); + xmlNewTextChild (priv->last_node, NULL, "double", str); + soup_xmlrpc_message_end_element (msg); + + g_free (str); +} + +void +soup_xmlrpc_message_write_time (SoupXmlrpcMessage *msg, const time_t timeval) +{ + SoupXmlrpcMessagePrivate *priv; + struct tm time; + char str[128]; + + g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); + priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (msg); + + soup_gmtime (&timeval, &time); + strftime (str, 128, "%Y%m%dT%H%M%s", &time); + + priv->last_node = xmlNewChild (priv->last_node, NULL, "value", NULL); + xmlNewTextChild (priv->last_node, NULL, "dateTime.iso8601", str); + soup_xmlrpc_message_end_element (msg); +} + +void +soup_xmlrpc_message_write_base64 (SoupXmlrpcMessage *msg, const char *buf, int len) +{ + SoupXmlrpcMessagePrivate *priv; + char *str; + + g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); + priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (msg); + + str = soup_base64_encode (buf, len); + + priv->last_node = xmlNewChild (priv->last_node, NULL, "value", NULL); + xmlNewTextChild (priv->last_node, NULL, "base64", str); + soup_xmlrpc_message_end_element (msg); + + g_free (str); +} + +void +soup_xmlrpc_message_start_struct (SoupXmlrpcMessage *msg) +{ + SoupXmlrpcMessagePrivate *priv; + + g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); + priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (msg); + + priv->last_node = xmlNewChild (priv->last_node, NULL, "value", NULL); + priv->last_node = xmlNewChild (priv->last_node, NULL, "struct", NULL); +} + +void +soup_xmlrpc_message_end_struct (SoupXmlrpcMessage *msg) +{ + g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); + + soup_xmlrpc_message_end_element (msg); + soup_xmlrpc_message_end_element (msg); +} + +void +soup_xmlrpc_message_start_member (SoupXmlrpcMessage *msg, const char *name) +{ + SoupXmlrpcMessagePrivate *priv; + + g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); + priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (msg); + + priv->last_node = xmlNewChild (priv->last_node, NULL, "member", NULL); +} + +void +soup_xmlrpc_message_end_member (SoupXmlrpcMessage *msg) +{ + g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); + + soup_xmlrpc_message_end_element (msg); +} + +void +soup_xmlrpc_message_start_array (SoupXmlrpcMessage *msg) +{ + SoupXmlrpcMessagePrivate *priv; + + g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); + priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (msg); + + priv->last_node = xmlNewChild (priv->last_node, NULL, "value", NULL); + priv->last_node = xmlNewChild (priv->last_node, NULL, "array", NULL); + priv->last_node = xmlNewChild (priv->last_node, NULL, "data", NULL); +} + +void +soup_xmlrpc_message_end_array (SoupXmlrpcMessage *msg) +{ + g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); + + soup_xmlrpc_message_end_element (msg); + soup_xmlrpc_message_end_element (msg); + soup_xmlrpc_message_end_element (msg); +} + +static void +soup_xmlrpc_message_end_element (SoupXmlrpcMessage *msg) +{ + SoupXmlrpcMessagePrivate *priv; + + g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); + priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (msg); + + priv->last_node = priv->last_node->parent; +} + +xmlChar * +soup_xmlrpc_message_to_string (SoupXmlrpcMessage *msg) +{ + SoupXmlrpcMessagePrivate *priv; + xmlChar *body; + unsigned int len; + + g_return_val_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg), NULL); + priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (msg); + + xmlDocDumpMemory (priv->doc, &body, &len); + + return body; +} + +void +soup_xmlrpc_message_persist (SoupXmlrpcMessage *msg) +{ + SoupXmlrpcMessagePrivate *priv; + xmlChar *body; + unsigned int len; + + g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); + priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (msg); + + xmlDocDumpMemory (priv->doc, &body, &len); + + soup_message_set_request (SOUP_MESSAGE (msg), "text/xml", SOUP_BUFFER_SYSTEM_OWNED, body, len); +} + +SoupXmlrpcResponse * +soup_xmlrpc_message_parse_response (SoupXmlrpcMessage *msg) +{ + char *str; + SoupXmlrpcResponse *response; + + g_return_val_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg), NULL); + + str = g_malloc0 (SOUP_MESSAGE (msg)->response.length + 1); + strncpy (str, SOUP_MESSAGE (msg)->response.body, SOUP_MESSAGE (msg)->response.length); + + response = soup_xmlrpc_response_new_from_string (str); + g_free (str); + + return response; +} diff --git a/libsoup/soup-xmlrpc-message.h b/libsoup/soup-xmlrpc-message.h new file mode 100644 index 00000000..9dd3f265 --- /dev/null +++ b/libsoup/soup-xmlrpc-message.h @@ -0,0 +1,75 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * FIXME Copyright + */ + +#ifndef SOUP_XMLRPC_MESSAGE_H +#define SOUP_XMLRPC_MESSAGE_H + +#include <time.h> +#include <libxml/tree.h> +#include <libsoup/soup-message.h> +#include <libsoup/soup-uri.h> + +#include "soup-xmlrpc-response.h" + +G_BEGIN_DECLS + +#define SOUP_TYPE_XMLRPC_MESSAGE (soup_xmlrpc_message_get_type ()) +#define SOUP_XMLRPC_MESSAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_XMLRPC_MESSAGE, SoupXmlrpcMessage)) +#define SOUP_XMLRPC_MESSAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_XMLRPC_MESSAGE, SoupXmlrpcMessageClass)) +#define SOUP_IS_XMLRPC_MESSAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_XMLRPC_MESSAGE)) +#define SOUP_IS_SOAP_MESSAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_XMLRPC_MESSAGE)) +#define SOUP_XMLRPC_MESSAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_XMLRPC_MESSAGE, SoupXmlrpcMessageClass)) + +typedef struct { + SoupMessage parent; + +} SoupXmlrpcMessage; + +typedef struct { + SoupMessageClass parent_class; +} SoupXmlrpcMessageClass; + +GType soup_xmlrpc_message_get_type (void); + +SoupXmlrpcMessage *soup_xmlrpc_message_new (const char *uri_string); +SoupXmlrpcMessage *soup_xmlrpc_message_new_from_uri (const SoupUri *uri); + +void soup_xmlrpc_message_start_call (SoupXmlrpcMessage *msg, + const char *method_name); +void soup_xmlrpc_message_end_call (SoupXmlrpcMessage *msg); + +void soup_xmlrpc_message_start_param (SoupXmlrpcMessage *msg); +void soup_xmlrpc_message_end_param (SoupXmlrpcMessage *msg); + +void soup_xmlrpc_message_write_int (SoupXmlrpcMessage *msg, + long i); +void soup_xmlrpc_message_write_boolean (SoupXmlrpcMessage *msg, + gboolean b); +void soup_xmlrpc_message_write_string (SoupXmlrpcMessage *msg, + const char *str); +void soup_xmlrpc_message_write_double (SoupXmlrpcMessage *msg, + double d); +void soup_xmlrpc_message_write_time (SoupXmlrpcMessage *msg, + const time_t timeval); +void soup_xmlrpc_message_write_base64 (SoupXmlrpcMessage *msg, + const char *buf, + int len); + +void soup_xmlrpc_message_start_struct (SoupXmlrpcMessage *msg); +void soup_xmlrpc_message_end_struct (SoupXmlrpcMessage *msg); + +void soup_xmlrpc_message_start_member (SoupXmlrpcMessage *msg, + const char *name); +void soup_xmlrpc_message_end_member (SoupXmlrpcMessage *msg); + +void soup_xmlrpc_message_start_array (SoupXmlrpcMessage *msg); +void soup_xmlrpc_message_end_array (SoupXmlrpcMessage *msg); + +xmlChar *soup_xmlrpc_message_to_string (SoupXmlrpcMessage *msg); +void soup_xmlrpc_message_persist (SoupXmlrpcMessage *msg); + +SoupXmlrpcResponse *soup_xmlrpc_message_parse_response (SoupXmlrpcMessage *msg); + +#endif diff --git a/libsoup/soup-xmlrpc-response.c b/libsoup/soup-xmlrpc-response.c new file mode 100644 index 00000000..16f85724 --- /dev/null +++ b/libsoup/soup-xmlrpc-response.c @@ -0,0 +1,635 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * soup-xmlrpc-response.c: XMLRPC response message + * + * Copyright (C) 2003, Novell, Inc. + * Copyright (C) 2004, Mariano Suarez-Alvarez <mariano@gnome.org> + * Copyright (C) 2004, Fernando Herrera <fherrera@onirica.com> + * Copyright (C) 2005, Jeff Bailey <jbailey@ubuntu.com> + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include <glib.h> +#include <libxml/tree.h> + +#include "soup-date.h" +#include "soup-misc.h" +#include "soup-xmlrpc-response.h" + + +G_DEFINE_TYPE (SoupXmlrpcResponse, soup_xmlrpc_response, G_TYPE_OBJECT) + +typedef struct { + xmlDocPtr doc; + gboolean fault; + xmlNodePtr value; +} SoupXmlrpcResponsePrivate; +#define SOUP_XMLRPC_RESPONSE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_XMLRPC_RESPONSE, SoupXmlrpcResponsePrivate)) + +static void +finalize (GObject *object) +{ + SoupXmlrpcResponsePrivate *priv = SOUP_XMLRPC_RESPONSE_GET_PRIVATE (object); + + if (priv->doc) + xmlFreeDoc (priv->doc); + + G_OBJECT_CLASS (soup_xmlrpc_response_parent_class)->finalize (object); +} + +static void +soup_xmlrpc_response_class_init (SoupXmlrpcResponseClass *soup_xmlrpc_response_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (soup_xmlrpc_response_class); + + g_type_class_add_private (soup_xmlrpc_response_class, sizeof (SoupXmlrpcResponsePrivate)); + + object_class->finalize = finalize; +} + +static void +soup_xmlrpc_response_init (SoupXmlrpcResponse *response) +{ + SoupXmlrpcResponsePrivate *priv = SOUP_XMLRPC_RESPONSE_GET_PRIVATE (response); + + priv->doc = xmlNewDoc ("1.0"); + priv->fault = FALSE; +} + + +SoupXmlrpcResponse * +soup_xmlrpc_response_new (void) +{ + SoupXmlrpcResponse *response; + + response = g_object_new (SOUP_TYPE_XMLRPC_RESPONSE, NULL); + return response; +} + +SoupXmlrpcResponse * +soup_xmlrpc_response_new_from_string (const char *xmlstr) +{ + SoupXmlrpcResponse *response; + + g_return_val_if_fail (xmlstr != NULL, NULL); + + response = g_object_new (SOUP_TYPE_XMLRPC_RESPONSE, NULL); + + if (!soup_xmlrpc_response_from_string (response, xmlstr)) { + g_object_unref (response); + return NULL; + } + + return response; +} + +static xmlNode * +exactly_one_child (xmlNode *node) +{ + xmlNode *child, *tmp; + + tmp = node->children; + while (tmp && xmlIsBlankNode (tmp)) + tmp = tmp->next; + + child = tmp; + if (tmp && tmp->next) { + tmp = tmp->next; + while (tmp && xmlIsBlankNode (tmp)) + tmp = tmp->next; + if (tmp) + return NULL; + } + + return child; +} + +gboolean +soup_xmlrpc_response_from_string (SoupXmlrpcResponse *response, const char *xmlstr) +{ + SoupXmlrpcResponsePrivate *priv; + xmlDocPtr newdoc; + xmlNodePtr body; + gboolean fault = TRUE; + + g_return_val_if_fail (SOUP_IS_XMLRPC_RESPONSE (response), FALSE); + priv = SOUP_XMLRPC_RESPONSE_GET_PRIVATE (response); + g_return_val_if_fail (xmlstr != NULL, FALSE); + + xmlKeepBlanksDefault (0); + newdoc = xmlParseMemory (xmlstr, strlen (xmlstr)); + if (!newdoc) + goto very_bad; + + body = xmlDocGetRootElement (newdoc); + if (!body || strcmp (body->name, "methodResponse")) + goto bad; + + body = exactly_one_child (body); + if (!body) + goto bad; + + if (strcmp (body->name, "params") == 0) { + fault = FALSE; + body = exactly_one_child (body); + if (!body || strcmp (body->name, "param")) + goto bad; + } else if (strcmp (body->name, "fault") != 0) + goto bad; + + body = exactly_one_child (body); + if (!body || strcmp (body->name, "value")) + goto bad; + + /* body should be pointing by now to the struct of a fault, or the value of a + * normal response + */ + + xmlFreeDoc (priv->doc); + priv->doc = newdoc; + priv->value = body; + + return TRUE; + +bad: + xmlFreeDoc (newdoc); +very_bad: + return FALSE; +} + +xmlChar * +soup_xmlrpc_response_to_string (SoupXmlrpcResponse *response) +{ + SoupXmlrpcResponsePrivate *priv; + xmlChar *str; + int size; + + g_return_val_if_fail (SOUP_IS_XMLRPC_RESPONSE (response), FALSE); + priv = SOUP_XMLRPC_RESPONSE_GET_PRIVATE (response); + + xmlDocDumpMemoryEnc (priv->doc, &str, &size, "UTF-8"); + + return str; +} + +SoupXmlrpcValue * +soup_xmlrpc_response_get_value (SoupXmlrpcResponse *response) +{ + SoupXmlrpcResponsePrivate *priv; + g_return_val_if_fail (SOUP_IS_XMLRPC_RESPONSE (response), FALSE); + priv = SOUP_XMLRPC_RESPONSE_GET_PRIVATE (response); + + return (SoupXmlrpcValue*) priv->value; +} + +SoupXmlrpcValueType +soup_xmlrpc_value_get_type (SoupXmlrpcValue *value) +{ + xmlNode *xml; + + xml = (xmlNode *) value; + + if (strcmp (xml->name, "value")) + return SOUP_XMLRPC_VALUE_TYPE_BAD; + + xml = exactly_one_child (xml); + if (!xml) + return SOUP_XMLRPC_VALUE_TYPE_BAD; + + if (strcmp (xml->name, "i4") == 0 || strcmp (xml->name, "int") == 0) + return SOUP_XMLRPC_VALUE_TYPE_INT; + else if (strcmp (xml->name, "boolean") == 0) + return SOUP_XMLRPC_VALUE_TYPE_BOOLEAN; + else if (strcmp (xml->name, "string") == 0) + return SOUP_XMLRPC_VALUE_TYPE_STRING; + else if (strcmp (xml->name, "double") == 0) + return SOUP_XMLRPC_VALUE_TYPE_DOUBLE; + else if (strcmp (xml->name, "dateTime.iso8601") == 0) + return SOUP_XMLRPC_VALUE_TYPE_DATETIME; + else if (strcmp (xml->name, "base64") == 0) + return SOUP_XMLRPC_VALUE_TYPE_BASE64; + else if (strcmp (xml->name, "struct") == 0) + return SOUP_XMLRPC_VALUE_TYPE_STRUCT; + else if (strcmp (xml->name, "array") == 0) + return SOUP_XMLRPC_VALUE_TYPE_ARRAY; + else + return SOUP_XMLRPC_VALUE_TYPE_BAD; +} + +gboolean +soup_xmlrpc_value_get_int (SoupXmlrpcValue *value, long *i) +{ + xmlNode *xml; + xmlChar *content; + char *tail; + + xml = (xmlNode *) value; + + if (strcmp (xml->name, "value")) + return FALSE; + xml = exactly_one_child (xml); + if (!xml || (strcmp (xml->name, "int") && strcmp (xml->name, "i4"))) + return FALSE; + + /* FIXME this should be exactly one text node */ + content = xmlNodeGetContent (xml); + *i = strtol (BAD_CAST (content), &tail, 10); + xmlFree (content); + + if (tail != '\0') + return FALSE; + else + return TRUE; +} + +gboolean +soup_xmlrpc_value_get_double (SoupXmlrpcValue *value, double *b) +{ + xmlNode *xml; + xmlChar *content; + char *tail; + + xml = (xmlNode *) value; + + if (strcmp (xml->name, "value")) + return FALSE; + xml = exactly_one_child (xml); + if (!xml || (strcmp (xml->name, "double"))) + return FALSE; + + /* FIXME this should be exactly one text node */ + content = xmlNodeGetContent (xml); + *b = g_ascii_strtod (BAD_CAST (content), &tail); + xmlFree (content); + + if (tail != '\0') + return FALSE; + else + return TRUE; +} + +gboolean +soup_xmlrpc_value_get_boolean (SoupXmlrpcValue *value, gboolean *b) +{ + xmlNode *xml; + xmlChar *content; + char *tail; + int i; + + xml = (xmlNode *) value; + + if (strcmp (xml->name, "value")) + return FALSE; + xml = exactly_one_child (xml); + if (!xml || strcmp (xml->name, "boolean")) + return FALSE; + + content = xmlNodeGetContent (xml); + i = strtol (BAD_CAST (content), &tail, 10); + xmlFree (content); + + if (tail != '\0') + return FALSE; + + if (i != 0 && i != 1) + return FALSE; + return i == 1; +} + +gboolean +soup_xmlrpc_value_get_string (SoupXmlrpcValue *value, char **str) +{ + xmlNode *xml; + xmlChar *content; + + xml = (xmlNode *) value; + + if (strcmp (xml->name, "value")) + return FALSE; + xml = exactly_one_child (xml); + if (!xml || strcmp (xml->name, "string")) + return FALSE; + + content = xmlNodeGetContent (xml); + *str = content ? g_strdup (content) : g_strdup (""); + xmlFree (content); + + return TRUE; +} + +gboolean +soup_xmlrpc_value_get_datetime (SoupXmlrpcValue *value, time_t *timeval) +{ + xmlNode *xml; + xmlChar *content; + char *ptr; + + xml = (xmlNode *) value; + + if (strcmp (xml->name, "value")) + return FALSE; + xml = exactly_one_child (xml); + if (!xml || (strcmp (xml->name, "dateTime.iso8601"))) + return FALSE; + + /* FIXME this should be exactly one text node */ + content = xmlNodeGetContent (xml); + ptr = BAD_CAST (content); + if (strlen (ptr) != 17) { + xmlFree (content); + return FALSE; + } + + *timeval = soup_date_iso8601_parse (ptr); + xmlFree (content); + return TRUE; +} + +gboolean +soup_xmlrpc_value_get_base64 (SoupXmlrpcValue *value, char **buf) +{ + xmlNode *xml; + xmlChar *content; + char *ret; + int inlen, state = 0, save = 0; + + + xml = (xmlNode *) value; + if (strcmp (xml->name, "value")) + return FALSE; + xml = exactly_one_child (xml); + if (!xml || strcmp (xml->name, "base64")) + return FALSE; + + content = xmlNodeGetContent (xml); + + /* FIXME If we can decode it, is it a valid base64? */ + inlen = strlen (content); + ret = g_malloc0 (inlen); + soup_base64_decode_step (content, inlen, ret, &state, &save); + g_free (ret); + if (state != 0) + return FALSE; + + *buf = content ? g_strdup (content) : g_strdup (""); + xmlFree (content); + + return TRUE; +} + + +gboolean +soup_xmlrpc_value_get_struct (SoupXmlrpcValue *value, GHashTable **table) +{ + xmlNode *xml; + GHashTable *t; + + xml = (xmlNode *) value; + + if (strcmp (xml->name, "value")) + return FALSE; + xml = exactly_one_child (xml); + + if (!xml || strcmp (xml->name, "struct")) + return FALSE; + + t = g_hash_table_new_full (g_str_hash, g_str_equal, xmlFree, NULL); + + for (xml = xml->children; xml; xml = xml->next) { + xmlChar *name; + xmlNode *val, *cur; + + if (strcmp (xml->name, "member") || !xml->children) + goto bad; + + name = NULL; + val = NULL; + + for (cur = xml->children; cur; cur = cur->next) { + if (strcmp(cur->name, "name") == 0) { + if (name) + goto local_bad; + name = xmlNodeGetContent (cur); + } + else if (strcmp (cur->name, "value") == 0) + val = cur; + else goto local_bad; + + continue; +local_bad: + if (name) xmlFree (name); + goto bad; + } + + if (!name || !val) { + if (name) xmlFree (name); + goto bad; + } + g_hash_table_insert (t, name, val); + } + + *table = t; + return TRUE; + +bad: + g_hash_table_destroy (t); + return FALSE; +} + +gboolean +soup_xmlrpc_value_array_get_iterator (SoupXmlrpcValue *value, SoupXmlrpcValueArrayIterator **iter) +{ + xmlNode *xml; + + xml = (xmlNode *) value; + + if (!xml->children || strcmp(xml->children->name, "data") == 0 || xml->children->next) + return FALSE; + + *iter = (SoupXmlrpcValueArrayIterator *) xml->children; + + return TRUE; +} + + +SoupXmlrpcValueArrayIterator * +soup_xmlrpc_value_array_iterator_prev (SoupXmlrpcValueArrayIterator *iter) +{ + xmlNode *xml; + + xml = (xmlNode *) iter; + + return (SoupXmlrpcValueArrayIterator *) xml->prev; +} + +SoupXmlrpcValueArrayIterator * +soup_xmlrpc_value_array_iterator_next (SoupXmlrpcValueArrayIterator *iter) +{ + xmlNode *xml; + + xml = (xmlNode *) iter; + + return (SoupXmlrpcValueArrayIterator *) xml->next; +} + +gboolean +soup_xmlrpc_value_array_iterator_get_value (SoupXmlrpcValueArrayIterator *iter, + SoupXmlrpcValue **value) +{ + xmlNode *xml; + + xml = (xmlNode *) iter; + + if (!xml || strcmp(xml->name, "data")) + return FALSE; + xml = exactly_one_child (xml); + if (!xml) + return FALSE; + + *value = (SoupXmlrpcValue *) xml; + + return TRUE; +} + +static void +indent (int d) +{ + while (d--) + g_printerr (" "); +} + +static void +soup_xmlrpc_value_dump_internal (SoupXmlrpcValue *value, int d); + +static void +soup_xmlrpc_value_dump_struct_member (const char *name, SoupXmlrpcValue *value, gpointer d) +{ + indent (GPOINTER_TO_INT (d)); + g_printerr ("MEMBER: %s\n", name); + soup_xmlrpc_value_dump_internal (value, GPOINTER_TO_INT (d)); +} + +static void +soup_xmlrpc_value_dump_array_element (const int i, SoupXmlrpcValue *value, gpointer d) +{ + indent (GPOINTER_TO_INT (d)); + g_printerr ("ELEMENT: %d\n", i); + soup_xmlrpc_value_dump_internal (value, GPOINTER_TO_INT (d)); +} + +static void +soup_xmlrpc_value_dump_internal (SoupXmlrpcValue *value, int d) +{ + long i; + gboolean b; + char *str; + double f; + time_t timeval; + GHashTable *hash; + SoupXmlrpcValueArrayIterator *iter; + + g_printerr ("\n\n[%s]\n", ((xmlNode*)value)->name); + switch (soup_xmlrpc_value_get_type (value)) { + + case SOUP_XMLRPC_VALUE_TYPE_BAD: + indent (d); + g_printerr ("BAD\n"); + break; + + case SOUP_XMLRPC_VALUE_TYPE_INT: + indent (d); + if (!soup_xmlrpc_value_get_int (value, &i)) + g_printerr ("BAD INT\n"); + else + g_printerr ("INT: %ld\n", i); + break; + + case SOUP_XMLRPC_VALUE_TYPE_BOOLEAN: + indent (d); + if (!soup_xmlrpc_value_get_boolean (value, &b)) + g_printerr ("BAD BOOLEAN\n"); + else + g_printerr ("BOOLEAN: %s\n", b ? "true" : "false"); + break; + + case SOUP_XMLRPC_VALUE_TYPE_STRING: + indent (d); + if (!soup_xmlrpc_value_get_string (value, &str)) + g_printerr ("BAD STRING\n"); + else { + g_printerr ("STRING: \"%s\"\n", str); + g_free (str); + } + break; + + case SOUP_XMLRPC_VALUE_TYPE_DOUBLE: + indent (d); + if (!soup_xmlrpc_value_get_double (value, &f)) + g_printerr ("BAD DOUBLE\n"); + else + g_printerr ("DOUBLE: %f\n", f); + break; + + case SOUP_XMLRPC_VALUE_TYPE_DATETIME: + indent (d); + if (!soup_xmlrpc_value_get_datetime (value, &timeval)) + g_printerr ("BAD DATETIME\n"); + else + g_printerr ("DATETIME: %s\n", asctime (gmtime (&timeval))); + break; + + case SOUP_XMLRPC_VALUE_TYPE_BASE64: + indent (d); + if (!soup_xmlrpc_value_get_base64 (value, &str)) + g_printerr ("BAD BASE64\n"); + else + g_printerr ("BASE64: %s\n", str); + + break; + + case SOUP_XMLRPC_VALUE_TYPE_STRUCT: + indent (d); + if (!soup_xmlrpc_value_get_struct (value, &hash)) + g_printerr ("BAD STRUCT\n"); + else { + g_printerr ("STRUCT\n"); + g_hash_table_foreach (hash, (GHFunc) soup_xmlrpc_value_dump_struct_member, + GINT_TO_POINTER (d+1)); + g_hash_table_destroy (hash); + } + break; + + case SOUP_XMLRPC_VALUE_TYPE_ARRAY: + indent (d); + if (!soup_xmlrpc_value_array_get_iterator (value, &iter)) + g_printerr ("BAD ARRAY\n"); + else { + g_printerr ("ARRAY\n"); + SoupXmlrpcValue *evalue; + int i = 0; + while (iter != NULL) { + soup_xmlrpc_value_array_iterator_get_value (iter, &evalue); + soup_xmlrpc_value_dump_array_element (i, evalue, GINT_TO_POINTER (d+1)); + iter = soup_xmlrpc_value_array_iterator_next (iter); + i++; + } + } + break; + } + +} + +void +soup_xmlrpc_value_dump (SoupXmlrpcValue *value) +{ + soup_xmlrpc_value_dump_internal (value, 0); +} + diff --git a/libsoup/soup-xmlrpc-response.h b/libsoup/soup-xmlrpc-response.h new file mode 100644 index 00000000..5f5b75b9 --- /dev/null +++ b/libsoup/soup-xmlrpc-response.h @@ -0,0 +1,87 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * FIXME Copyright + */ + +#ifndef SOUP_XMLRPC_RESPONSE_H +#define SOUP_XMLRPC_RESPONSE_H + +#include <glib-object.h> +#include <libxml/tree.h> + +G_BEGIN_DECLS + +#define SOUP_TYPE_XMLRPC_RESPONSE (soup_xmlrpc_response_get_type ()) +#define SOUP_XMLRPC_RESPONSE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_XMLRPC_RESPONSE, SoupXmlrpcResponse)) +#define SOUP_XMLRPC_RESPONSE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_XMLRPC_RESPONSE, SoupXmlrpcResponseClass)) +#define SOUP_IS_XMLRPC_RESPONSE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_XMLRPC_RESPONSE)) +#define SOUP_IS_XMLRPC_RESPONSE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SOUP_TYPE_XMLRPC_RESPONSE)) +#define SOUP_XMLRPC_RESPONSE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_XMLRPC_RESPONSE, SoupXmlrpcResponseClass)) + +typedef struct { + GObject parent; + +} SoupXmlrpcResponse; + +typedef struct { + GObjectClass parent_class; +} SoupXmlrpcResponseClass; + +GType soup_xmlrpc_response_get_type (void); + +SoupXmlrpcResponse *soup_xmlrpc_response_new (void); +SoupXmlrpcResponse *soup_xmlrpc_response_new_from_string (const char *xmlstr); + +gboolean soup_xmlrpc_response_from_string (SoupXmlrpcResponse *response, + const char *xmlstr); +xmlChar *soup_xmlrpc_response_to_string (SoupXmlrpcResponse *response); + +typedef xmlNode *SoupXmlrpcValue; + +typedef enum { + SOUP_XMLRPC_VALUE_TYPE_BAD, + SOUP_XMLRPC_VALUE_TYPE_INT, + SOUP_XMLRPC_VALUE_TYPE_BOOLEAN, + SOUP_XMLRPC_VALUE_TYPE_STRING, + SOUP_XMLRPC_VALUE_TYPE_DOUBLE, + SOUP_XMLRPC_VALUE_TYPE_DATETIME, + SOUP_XMLRPC_VALUE_TYPE_BASE64, + SOUP_XMLRPC_VALUE_TYPE_STRUCT, + SOUP_XMLRPC_VALUE_TYPE_ARRAY +} SoupXmlrpcValueType; + +SoupXmlrpcValue *soup_xmlrpc_response_get_value (SoupXmlrpcResponse *response); +SoupXmlrpcValueType soup_xmlrpc_value_get_type (SoupXmlrpcValue *value); + +gboolean soup_xmlrpc_value_get_int (SoupXmlrpcValue *value, + long *i); +gboolean soup_xmlrpc_value_get_double (SoupXmlrpcValue *value, + double *b); +gboolean soup_xmlrpc_value_get_boolean (SoupXmlrpcValue *value, + gboolean *b); +gboolean soup_xmlrpc_value_get_string (SoupXmlrpcValue *value, + char **str); +gboolean soup_xmlrpc_value_get_datetime (SoupXmlrpcValue *value, + time_t *timeval); +gboolean soup_xmlrpc_value_get_base64 (SoupXmlrpcValue *value, + char **buf); + +gboolean soup_xmlrpc_value_get_struct (SoupXmlrpcValue *value, + GHashTable **table); + + +typedef xmlNodePtr SoupXmlrpcValueArrayIterator; + +gboolean soup_xmlrpc_value_array_get_iterator (SoupXmlrpcValue *value, + SoupXmlrpcValueArrayIterator **iter); + +SoupXmlrpcValueArrayIterator *soup_xmlrpc_value_array_iterator_prev (SoupXmlrpcValueArrayIterator *iter); +SoupXmlrpcValueArrayIterator *soup_xmlrpc_value_array_iterator_next (SoupXmlrpcValueArrayIterator *iter); +gboolean soup_xmlrpc_value_array_iterator_get_value (SoupXmlrpcValueArrayIterator *iter, + SoupXmlrpcValue **value); + +void soup_xmlrpc_value_dump (SoupXmlrpcValue *value); + +G_END_DECLS + +#endif diff --git a/tests/.cvsignore b/tests/.cvsignore index 674da08d..4aa8516e 100644 --- a/tests/.cvsignore +++ b/tests/.cvsignore @@ -1,9 +1,11 @@ Makefile Makefile.in auth-test +date dict dns get +getbug revserver simple-httpd simple-proxy diff --git a/tests/Makefile.am b/tests/Makefile.am index 1d7ed7f9..331ff4f0 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -7,18 +7,22 @@ LIBS = $(top_builddir)/libsoup/libsoup-$(SOUP_API_VERSION).la noinst_PROGRAMS = \ auth-test \ + date \ dict \ dns \ get \ + getbug \ revserver \ simple-httpd \ simple-proxy \ uri-parsing auth_test_SOURCES = auth-test.c +date_SOURCES = date.c dict_SOURCES = dict.c dns_SOURCES = dns.c get_SOURCES = get.c +getbug_SOURCES = getbug.c revserver_SOURCES = revserver.c simple_httpd_SOURCES = simple-httpd.c simple_proxy_SOURCES = simple-proxy.c diff --git a/tests/date.c b/tests/date.c new file mode 100644 index 00000000..c8054526 --- /dev/null +++ b/tests/date.c @@ -0,0 +1,55 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2005 Novell, Inc. + */ + +#include <stdio.h> +#include <string.h> + +#include <libsoup/soup-date.h> + +static int errors = 0; + +#define RFC1123_DATE "Sun, 06 Nov 1994 08:49:37 GMT" +#define RFC850_DATE "Sunday, 06-Nov-94 08:49:37 GMT" +#define ASCTIME_DATE "Sun Nov 6 08:49:37 1994" +#define ISO8601_DATE "1994-11-06T08:49:37" + +#define EXPECTED 784111777 + +static void +check (const char *test, const char *date, time_t got) +{ + if (got == EXPECTED) + return; + + fprintf (stderr, "%s date parsing failed for '%s'.\n", test, date); + fprintf (stderr, " expected: %lu, got: %lu\n\n", + (unsigned long)EXPECTED, (unsigned long)got); + errors++; +} + +int +main (int argc, char **argv) +{ + char *date; + + check ("RFC1123", RFC1123_DATE, soup_date_parse (RFC1123_DATE)); + check ("RFC850", RFC850_DATE, soup_date_parse (RFC850_DATE)); + check ("asctime", ASCTIME_DATE, soup_date_parse (ASCTIME_DATE)); + check ("iso8610", ISO8601_DATE, soup_date_iso8601_parse (ISO8601_DATE)); + + date = soup_date_generate (EXPECTED); + if (strcmp (date, RFC1123_DATE) != 0) { + fprintf (stderr, "date generation failed.\n"); + fprintf (stderr, " expected: %s\n got: %s\n\n", + RFC1123_DATE, date); + errors++; + } + + if (errors == 0) + printf ("OK\n"); + else + fprintf (stderr, "%d errors\n", errors); + return errors; +} diff --git a/tests/getbug.c b/tests/getbug.c new file mode 100644 index 00000000..058eb000 --- /dev/null +++ b/tests/getbug.c @@ -0,0 +1,137 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2001-2003, Ximian, Inc. + */ + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <libsoup/soup.h> +#include <libsoup/soup-xmlrpc-message.h> +#include <libsoup/soup-xmlrpc-response.h> + +SoupSession *session; +GMainLoop *loop; + +static void +print_struct_field (gpointer key, gpointer value, gpointer data) +{ + char *str; + if (soup_xmlrpc_value_get_string (value, &str)) + printf ("%s: %s\n", (char *)key, str); +} + +static void +got_response (SoupMessage *msg, gpointer user_data) +{ + SoupXmlrpcResponse *response; + SoupXmlrpcValue *value; + GHashTable *hash; + + if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { + fprintf (stderr, "%d %s\n", msg->status_code, msg->reason_phrase); + exit (1); + } + + response = soup_xmlrpc_message_parse_response (SOUP_XMLRPC_MESSAGE (msg)); + if (!response) { + fprintf (stderr, "Could not parse XMLRPC response\n"); + exit (1); + } + + value = soup_xmlrpc_response_get_value (response); + if (!value) { + fprintf (stderr, "No response value in XMLRPC response\n"); + exit (1); + } + + if (!soup_xmlrpc_value_get_struct (value, &hash)) { + fprintf (stderr, "Could not extract result from XMLRPC response\n"); + exit (1); + } + + g_hash_table_foreach (hash, print_struct_field, NULL); + g_hash_table_destroy (hash); + + g_object_unref (response); + g_main_quit (loop); +} + +static void +usage (void) +{ + fprintf (stderr, "Usage: getbug [-p proxy_uri] [bugzilla-uri] bug-number\n"); + exit (1); +} + +int +main (int argc, char **argv) +{ + SoupUri *proxy = NULL; + SoupXmlrpcMessage *msg; + char *uri = "http://bugzilla.redhat.com/bugzilla/xmlrpc.cgi"; + int opt, bug; + + g_type_init (); + g_thread_init (NULL); + + while ((opt = getopt (argc, argv, "p:")) != -1) { + switch (opt) { + case 'p': + proxy = soup_uri_new (optarg); + if (!proxy) { + fprintf (stderr, "Could not parse %s as URI\n", + optarg); + exit (1); + } + break; + + case '?': + usage (); + break; + } + } + argc -= optind; + argv += optind; + + if (argc > 1) { + uri = argv[0]; + argc--; + argv++; + } + + if (argc != 1 || (bug = atoi (argv[0])) == 0) + usage (); + + session = soup_session_async_new_with_options ( + SOUP_SESSION_PROXY_URI, proxy, + NULL); + + msg = soup_xmlrpc_message_new (uri); + if (!msg) { + fprintf (stderr, "Could not create web service request to '%s'\n", uri); + exit (1); + } + + soup_xmlrpc_message_start_call (msg, "bugzilla.getBug"); + soup_xmlrpc_message_start_param (msg); + soup_xmlrpc_message_write_int (msg, bug); + soup_xmlrpc_message_end_param (msg); + soup_xmlrpc_message_end_call (msg); + + soup_xmlrpc_message_persist (msg); + soup_session_queue_message (session, SOUP_MESSAGE (msg), + got_response, NULL); + + loop = g_main_loop_new (NULL, TRUE); + g_main_run (loop); + g_main_loop_unref (loop); + + return 0; +} |