summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog19
-rw-r--r--TODO10
-rw-r--r--configure.in5
-rw-r--r--libsoup/Makefile.am10
-rw-r--r--libsoup/soup-date.c261
-rw-r--r--libsoup/soup-date.h19
-rw-r--r--libsoup/soup-xmlrpc-message.c359
-rw-r--r--libsoup/soup-xmlrpc-message.h75
-rw-r--r--libsoup/soup-xmlrpc-response.c635
-rw-r--r--libsoup/soup-xmlrpc-response.h87
-rw-r--r--tests/.cvsignore2
-rw-r--r--tests/Makefile.am4
-rw-r--r--tests/date.c55
-rw-r--r--tests/getbug.c137
14 files changed, 1670 insertions, 8 deletions
diff --git a/ChangeLog b/ChangeLog
index c4705d9c..72799996 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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
diff --git a/TODO b/TODO
index caac10a8..89791567 100644
--- a/TODO
+++ b/TODO
@@ -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 **)&timestamp, 10);
+ if (*timestamp == '-') {
+ // YYYY-MM-DD
+ tm.tm_year = val - 1900;
+ timestamp++;
+ tm.tm_mon = strtoul (timestamp, (char **)&timestamp, 10) - 1;
+ if (*timestamp++ != '-')
+ return -1;
+ tm.tm_mday = strtoul (timestamp, (char **)&timestamp, 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 **)&timestamp, 10);
+ if (*timestamp == ':') {
+ // hh:mm:ss
+ tm.tm_hour = val;
+ timestamp++;
+ tm.tm_min = strtoul (timestamp, (char **)&timestamp, 10);
+ if (*timestamp++ != ':')
+ return -1;
+ tm.tm_sec = strtoul (timestamp, (char **)&timestamp, 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 **)&timestamp, 10);
+
+ if (*timestamp == '+' || *timestamp == '-') {
+ int sign = (*timestamp == '+') ? -1 : 1;
+ val = strtoul (timestamp + 1, (char **)&timestamp, 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;
+}