fix mutex use to avoid a race condition
authorDan Winship <danw@src.gnome.org>
Sun, 27 Jan 2008 19:21:30 +0000 (19:21 +0000)
committerDan Winship <danw@src.gnome.org>
Sun, 27 Jan 2008 19:21:30 +0000 (19:21 +0000)
* libsoup/soup-dns.c (resolver_thread): fix mutex use to avoid a
race condition

* libsoup/soup-xmlrpc.c (soup_xmlrpc_build_faultv):
(soup_xmlrpc_set_response, soup_xmlrpc_set_fault):
(soup_xmlrpc_parse_method_call): Fix misc server-side stuff
(soup_xmlrpc_parse_method_response): Fix fault parsing

* libsoup/soup-xmlrpc.h (SoupXMLRPCFault): add semi-standard fault
codes from
http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php

* tests/xmlrpc-server.php (sum): return a <fault> if the arguments
are wrong (so that xmlrpc-test can test that case).
(dateChange): change to take two parameters, a date and a struct,
instead of putting the date in the struct, since we weren't
previously testing multiple parameter handling.

* tests/xmlrpc-test.c (main): Add a -u flag to specify an
alternate URL.
(do_xmlrpc): Remove level 3 debug output, which is now redundant
with the SoupLogger stuff.
(test_dateChange): update for dateChange prototype change
(test_fault_malformed, test_fault_method, test_fault_args): test
handling of faults

* tests/xmlrpc-server-test.c: Test the server-side XML-RPC API (by
implementing the same methods as xmlrpc-server.php and then
using xmlrpc-test)

svn path=/trunk/; revision=1056

ChangeLog
libsoup/soup-dns.c
libsoup/soup-xmlrpc.c
libsoup/soup-xmlrpc.h
tests/Makefile.am
tests/libsoup.supp
tests/xmlrpc-server-test.c [new file with mode: 0644]
tests/xmlrpc-server.php
tests/xmlrpc-test.c

index 711a058..d3465c3 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,37 @@
 2008-01-27  Dan Winship  <danw@gnome.org>
 
+       * libsoup/soup-dns.c (resolver_thread): fix mutex use to avoid a
+       race condition
+
+       * libsoup/soup-xmlrpc.c (soup_xmlrpc_build_faultv):
+       (soup_xmlrpc_set_response, soup_xmlrpc_set_fault):
+       (soup_xmlrpc_parse_method_call): Fix misc server-side stuff
+       (soup_xmlrpc_parse_method_response): Fix fault parsing
+
+       * libsoup/soup-xmlrpc.h (SoupXMLRPCFault): add semi-standard fault
+       codes from
+       http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
+
+       * tests/xmlrpc-server.php (sum): return a <fault> if the arguments
+       are wrong (so that xmlrpc-test can test that case).
+       (dateChange): change to take two parameters, a date and a struct,
+       instead of putting the date in the struct, since we weren't
+       previously testing multiple parameter handling.
+
+       * tests/xmlrpc-test.c (main): Add a -u flag to specify an
+       alternate URL.
+       (do_xmlrpc): Remove level 3 debug output, which is now redundant
+       with the SoupLogger stuff.
+       (test_dateChange): update for dateChange prototype change
+       (test_fault_malformed, test_fault_method, test_fault_args): test
+       handling of faults
+
+       * tests/xmlrpc-server-test.c: Test the server-side XML-RPC API (by
+       implementing the same methods as xmlrpc-server.php and then
+       using xmlrpc-test)
+
+2008-01-27  Dan Winship  <danw@gnome.org>
+
        * libsoup/soup-headers.c (soup_header_parse_quality_list): fix to
        not sometimes read beyond the end of the string.
 
index 31d1d20..709f43e 100644 (file)
@@ -517,9 +517,9 @@ resolver_thread (gpointer user_data)
        else if (entry->sockaddr == NULL)
                resolve_address (entry);
 
+       g_mutex_lock (soup_dns_lock);
        entry->resolver_thread = NULL;
 
-       g_mutex_lock (soup_dns_lock);
        async_lookups = entry->async_lookups;
        entry->async_lookups = NULL;
        g_mutex_unlock (soup_dns_lock);
index 9ebfe83..3cb32ef 100644 (file)
@@ -301,23 +301,23 @@ soup_xmlrpc_build_faultv (int fault_code, const char *fault_format, va_list args
        node = xmlNewDocNode (doc, NULL,
                              (const xmlChar *)"methodResponse", NULL);
        xmlDocSetRootElement (doc, node);
-       node = xmlNewDocNode (doc, NULL, (const xmlChar *)"fault", NULL);
-       node = xmlNewDocNode (doc, NULL, (const xmlChar *)"value", NULL);
-       node = xmlNewDocNode (doc, NULL, (const xmlChar *)"struct", NULL);
+       node = xmlNewChild (node, NULL, (const xmlChar *)"fault", NULL);
+       node = xmlNewChild (node, NULL, (const xmlChar *)"value", NULL);
+       node = xmlNewChild (node, NULL, (const xmlChar *)"struct", NULL);
 
        memset (&value, 0, sizeof (value));
 
-       member = xmlNewDocNode (doc, NULL, (const xmlChar *)"member", NULL);
-       xmlNewDocNode (doc, NULL,
-                      (const xmlChar *)"name", (const xmlChar *)"faultCode");
+       member = xmlNewChild (node, NULL, (const xmlChar *)"member", NULL);
+       xmlNewChild (member, NULL,
+                    (const xmlChar *)"name", (const xmlChar *)"faultCode");
        g_value_init (&value, G_TYPE_INT);
        g_value_set_int (&value, fault_code);
        insert_value (member, &value);
        g_value_unset (&value);
 
-       member = xmlNewDocNode (doc, NULL, (const xmlChar *)"member", NULL);
-       xmlNewDocNode (doc, NULL,
-                      (const xmlChar *)"name", (const xmlChar *)"faultString");
+       member = xmlNewChild (node, NULL, (const xmlChar *)"member", NULL);
+       xmlNewChild (member, NULL,
+                    (const xmlChar *)"name", (const xmlChar *)"faultString");
        g_value_init (&value, G_TYPE_STRING);
        g_value_take_string (&value, fault_string);
        insert_value (member, &value);
@@ -373,14 +373,14 @@ soup_xmlrpc_set_response (SoupMessage *msg, GType type, ...)
        char *body;
 
        va_start (args, type);
-       SOUP_VALUE_GETV (&value, type, args);
+       SOUP_VALUE_SETV (&value, type, args);
        va_end (args);
 
        body = soup_xmlrpc_build_method_response (&value);
        g_value_unset (&value);
        soup_message_set_status (msg, SOUP_STATUS_OK);
-       soup_message_set_request (msg, "text/xml", SOUP_MEMORY_TAKE,
-                                 body, strlen (body));
+       soup_message_set_response (msg, "text/xml", SOUP_MEMORY_TAKE,
+                                  body, strlen (body));
 }
 
 /**
@@ -406,8 +406,8 @@ soup_xmlrpc_set_fault (SoupMessage *msg, int fault_code,
        va_end (args);
 
        soup_message_set_status (msg, SOUP_STATUS_OK);
-       soup_message_set_request (msg, "text/xml", SOUP_MEMORY_TAKE,
-                                 body, strlen (body));
+       soup_message_set_response (msg, "text/xml", SOUP_MEMORY_TAKE,
+                                  body, strlen (body));
 }
 
 
@@ -586,13 +586,15 @@ soup_xmlrpc_parse_method_call (const char *method_call, int length,
        param = find_real_node (node->children);
        while (param && !strcmp ((const char *)param->name, "param")) {
                xval = find_real_node (param->children);
-               if (!xval || !strcmp ((const char *)xval->name, "value") ||
+               if (!xval || strcmp ((const char *)xval->name, "value") != 0 ||
                    !parse_value (xval, &value)) {
                        g_value_array_free (*params);
                        goto fail;
                }
                g_value_array_append (*params, &value);
                g_value_unset (&value);
+
+               param = find_real_node (param->next);
        }
 
        success = TRUE;
@@ -685,30 +687,32 @@ soup_xmlrpc_parse_method_response (const char *method_response, int length,
                goto fail;
 
        if (!strcmp ((const char *)node->name, "fault") && error) {
-               int fault_code = -1;
-               xmlChar *fault_string = NULL;
-
-               for (node = find_real_node (node->children);
-                    node;
-                    node = find_real_node (node->next)) {
-                       if (!strcmp ((const char *)node->name, "faultCode")) {
-                               xmlChar *content = xmlNodeGetContent (node);
-                               fault_code = atoi ((char *)content);
-                               xmlFree (content);
-                       } else if (!strcmp ((const char *)node->name, "faultString")) {
-                               fault_string = xmlNodeGetContent (node);
-                       } else {
-                               if (fault_string)
-                                       xmlFree (fault_string);
-                               goto fail;
-                       }
+               int fault_code;
+               char *fault_string;
+               GValue fault_val;
+               GHashTable *fault_hash;
+
+               node = find_real_node (node->children);
+               if (!node || strcmp ((const char *)node->name, "value") != 0)
+                       goto fail;
+               if (!parse_value (node, &fault_val))
+                       goto fail;
+               if (!G_VALUE_HOLDS (&fault_val, G_TYPE_HASH_TABLE)) {
+                       g_value_unset (&fault_val);
+                       goto fail;
                }
-               if (fault_code != -1 && fault_string != NULL) {
-                       g_set_error (error, SOUP_XMLRPC_FAULT,
-                                    fault_code, "%s", fault_string);
+               fault_hash = g_value_get_boxed (&fault_val);
+               if (!soup_value_hash_lookup (fault_hash, "faultCode",
+                                            G_TYPE_INT, &fault_code) ||
+                   !soup_value_hash_lookup (fault_hash, "faultString",
+                                            G_TYPE_STRING, &fault_string)) {
+                       g_value_unset (&fault_val);
+                       goto fail;
                }
-               if (fault_string)
-                       xmlFree (fault_string);
+
+               g_set_error (error, SOUP_XMLRPC_FAULT,
+                            fault_code, "%s", fault_string);
+               g_value_unset (&fault_val);
        } else if (!strcmp ((const char *)node->name, "params")) {
                node = find_real_node (node->children);
                if (!node || strcmp ((const char *)node->name, "param") != 0)
index faf84f6..3d1066d 100644 (file)
@@ -61,6 +61,22 @@ typedef enum {
 #define SOUP_XMLRPC_FAULT soup_xmlrpc_fault_quark()
 GQuark soup_xmlrpc_fault_quark (void);
 
+/* From http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php.
+ * These are an extension, not part of the XML-RPC spec; you can't
+ * assume servers will use them.
+ */
+typedef enum {
+       SOUP_XMLRPC_FAULT_PARSE_ERROR_NOT_WELL_FORMED = -32700,
+       SOUP_XMLRPC_FAULT_PARSE_ERROR_UNSUPPORTED_ENCODING = -32701,
+       SOUP_XMLRPC_FAULT_PARSE_ERROR_INVALID_CHARACTER_FOR_ENCODING = -32702,
+       SOUP_XMLRPC_FAULT_SERVER_ERROR_INVALID_XML_RPC = -32600,
+       SOUP_XMLRPC_FAULT_SERVER_ERROR_REQUESTED_METHOD_NOT_FOUND = -32601,
+       SOUP_XMLRPC_FAULT_SERVER_ERROR_INVALID_METHOD_PARAMETERS = -32602,
+       SOUP_XMLRPC_FAULT_SERVER_ERROR_INTERNAL_XML_RPC_ERROR = -32603,
+       SOUP_XMLRPC_FAULT_APPLICATION_ERROR = -32500,
+       SOUP_XMLRPC_FAULT_SYSTEM_ERROR = -32400,
+       SOUP_XMLRPC_FAULT_TRANSPORT_ERROR = -32300,
+} SoupXMLRPCFault;
 
 G_END_DECLS
 
index 3f9ce55..4ce7ac9 100644 (file)
@@ -44,6 +44,7 @@ simple_proxy_SOURCES = simple-proxy.c
 ssl_test_SOURCES = ssl-test.c $(TEST_SRCS)
 uri_parsing_SOURCES = uri-parsing.c $(TEST_SRCS)
 xmlrpc_test_SOURCES = xmlrpc-test.c $(TEST_SRCS)
+xmlrpc_server_test_SOURCES = xmlrpc-server-test.c $(TEST_SRCS)
 
 if HAVE_APACHE
 APACHE_TESTS = auth-test proxy-test pull-api
@@ -55,7 +56,7 @@ if HAVE_SSL
 SSL_TESTS = ssl-test
 endif
 if HAVE_XMLRPC_EPI_PHP
-XMLRPC_TESTS = xmlrpc-test
+XMLRPC_TESTS = xmlrpc-test xmlrpc-server-test
 endif
 
 TESTS =                        \
index eb14a5a..0e395b1 100644 (file)
    fun:g_child_watch_source_init_multi_threaded
    fun:g_child_watch_source_init
 }
+{
+   glib/childwatch2b
+   Memcheck:Leak
+   fun:calloc
+   fun:_dl_allocate_tls
+   fun:pthread_create@@GLIBC_2.2.5
+   fun:g_thread_create_posix_impl
+   fun:g_thread_create_full
+   fun:g_child_watch_source_init_multi_threaded
+   fun:g_child_watch_source_init
+}
 
 
 {
diff --git a/tests/xmlrpc-server-test.c b/tests/xmlrpc-server-test.c
new file mode 100644 (file)
index 0000000..cad0a8e
--- /dev/null
@@ -0,0 +1,337 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2008 Red Hat, Inc.
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/wait.h>
+
+#include <libsoup/soup.h>
+
+#include "test-utils.h"
+
+GMainLoop *loop;
+
+static void
+type_error (SoupMessage *msg, GType expected, GValueArray *params, int bad_value)
+{
+       soup_xmlrpc_set_fault (msg,
+                              SOUP_XMLRPC_FAULT_SERVER_ERROR_INVALID_METHOD_PARAMETERS,
+                              "Bad parameter #%d: expected %s, got %s",
+                              bad_value + 1, g_type_name (expected),
+                              g_type_name (G_VALUE_TYPE (&params->values[bad_value])));
+}
+
+static void
+args_error (SoupMessage *msg, GValueArray *params, int expected)
+{
+       soup_xmlrpc_set_fault (msg,
+                              SOUP_XMLRPC_FAULT_SERVER_ERROR_INVALID_METHOD_PARAMETERS,
+                              "Wrong number of parameters: expected %d, got %d",
+                              expected, params->n_values);
+}
+
+static void
+do_sum (SoupMessage *msg, GValueArray *params)
+{
+       int sum = 0, i, val;
+       GValueArray *nums;
+
+       if (params->n_values != 1) {
+               args_error (msg, params, 1);
+               return;
+       }
+       if (!soup_value_array_get_nth (params, 0, G_TYPE_VALUE_ARRAY, &nums)) {
+               type_error (msg, G_TYPE_VALUE_ARRAY, params, 0);
+               return;
+       }
+
+       for (i = 0; i < nums->n_values; i++) {
+               if (!soup_value_array_get_nth (nums, i, G_TYPE_INT, &val)) {
+                       type_error (msg, G_TYPE_INT, nums, i);
+                       return;
+               }
+               sum += val;
+       }
+
+       soup_xmlrpc_set_response (msg, G_TYPE_INT, sum);
+
+}
+
+static void
+do_countBools (SoupMessage *msg, GValueArray *params)
+{
+       int i, trues = 0, falses = 0;
+       GValueArray *bools;
+       GHashTable *ret = soup_value_hash_new ();
+       gboolean val;
+
+       if (params->n_values != 1) {
+               args_error (msg, params, 1);
+               return;
+       }
+       if (!soup_value_array_get_nth (params, 0, G_TYPE_VALUE_ARRAY, &bools)) {
+               type_error (msg, G_TYPE_VALUE_ARRAY, params, 0);
+               return;
+       }
+
+       for (i = 0; i < bools->n_values; i++) {
+               if (!soup_value_array_get_nth (bools, i, G_TYPE_BOOLEAN, &val)) {
+                       type_error (msg, G_TYPE_BOOLEAN, params, i);
+                       return;
+               }
+               if (val)
+                       trues++;
+               else
+                       falses++;
+       }
+
+       soup_value_hash_insert (ret, "true", G_TYPE_INT, trues);
+       soup_value_hash_insert (ret, "false", G_TYPE_INT, falses);
+       soup_xmlrpc_set_response (msg, G_TYPE_HASH_TABLE, ret);
+       g_hash_table_destroy (ret);
+
+}
+
+static void
+do_md5sum (SoupMessage *msg, GValueArray *params)
+{
+       GChecksum *checksum;
+       GByteArray *data, *digest;
+       gsize digest_len = 16;
+
+       if (params->n_values != 1) {
+               args_error (msg, params, 1);
+               return;
+       }
+
+       if (!soup_value_array_get_nth (params, 0, SOUP_TYPE_BYTE_ARRAY, &data)) {
+               type_error (msg, SOUP_TYPE_BYTE_ARRAY, params, 0);
+               return;
+       }
+       checksum = g_checksum_new (G_CHECKSUM_MD5);
+       g_checksum_update (checksum, data->data, data->len);
+       digest = g_byte_array_new ();
+       g_byte_array_set_size (digest, digest_len);
+       g_checksum_get_digest (checksum, digest->data, &digest_len);
+       g_checksum_free (checksum);
+
+       soup_xmlrpc_set_response (msg, SOUP_TYPE_BYTE_ARRAY, digest);
+       g_byte_array_free (digest, TRUE);
+}
+
+
+static void
+do_dateChange (SoupMessage *msg, GValueArray *params)
+{
+       GHashTable *arg;
+       SoupDate *date;
+       int val;
+
+       if (params->n_values != 2) {
+               args_error (msg, params, 2);
+               return;
+       }
+
+       if (!soup_value_array_get_nth (params, 0, SOUP_TYPE_DATE, &date)) {
+               type_error (msg, SOUP_TYPE_DATE, params, 0);
+               return;
+       }
+       if (!soup_value_array_get_nth (params, 1, G_TYPE_HASH_TABLE, &arg)) {
+               type_error (msg, G_TYPE_HASH_TABLE, params, 1);
+               return;
+       }
+
+       if (soup_value_hash_lookup (arg, "tm_year", G_TYPE_INT, &val))
+               date->year = val + 1900;
+       if (soup_value_hash_lookup (arg, "tm_mon", G_TYPE_INT, &val))
+               date->month = val + 1;
+       if (soup_value_hash_lookup (arg, "tm_mday", G_TYPE_INT, &val))
+               date->day = val;
+       if (soup_value_hash_lookup (arg, "tm_hour", G_TYPE_INT, &val))
+               date->hour = val;
+       if (soup_value_hash_lookup (arg, "tm_min", G_TYPE_INT, &val))
+               date->minute = val;
+       if (soup_value_hash_lookup (arg, "tm_sec", G_TYPE_INT, &val))
+               date->second = val;
+
+       soup_xmlrpc_set_response (msg, SOUP_TYPE_DATE, date);
+}
+
+static void
+do_echo (SoupMessage *msg, GValueArray *params)
+{
+       int i;
+       const char *val;
+       GValueArray *in, *out;
+
+       if (!soup_value_array_get_nth (params, 0, G_TYPE_VALUE_ARRAY, &in)) {
+               type_error (msg, G_TYPE_VALUE_ARRAY, params, 0);
+               return;
+       }
+
+       out = g_value_array_new (in->n_values);
+       for (i = 0; i < in->n_values; i++) {
+               if (!soup_value_array_get_nth (in, i, G_TYPE_STRING, &val)) {
+                       type_error (msg, G_TYPE_STRING, in, i);
+                       return;
+               }
+               soup_value_array_append (out, G_TYPE_STRING, val);
+       }
+
+       soup_xmlrpc_set_response (msg, G_TYPE_VALUE_ARRAY, out);
+       g_value_array_free (out);
+
+}
+
+static void
+server_callback (SoupServer *server, SoupMessage *msg,
+                const char *path, GHashTable *query,
+                SoupClientContext *context, gpointer data)
+{
+       char *method_name;
+       GValueArray *params;
+
+       if (msg->method != SOUP_METHOD_POST) {
+               soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
+               return;
+       }
+
+       soup_message_set_status (msg, SOUP_STATUS_OK);
+
+       if (!soup_xmlrpc_parse_method_call (msg->request_body->data,
+                                           msg->request_body->length,
+                                           &method_name, &params)) {
+               soup_xmlrpc_set_fault (msg, SOUP_XMLRPC_FAULT_PARSE_ERROR_NOT_WELL_FORMED,
+                                      "Could not parse method call");
+               return;
+       }
+
+       if (!strcmp (method_name, "sum"))
+               do_sum (msg, params);
+       else if (!strcmp (method_name, "countBools"))
+               do_countBools (msg, params);
+       else if (!strcmp (method_name, "md5sum"))
+               do_md5sum (msg, params);
+       else if (!strcmp (method_name, "dateChange"))
+               do_dateChange (msg, params);
+       else if (!strcmp (method_name, "echo"))
+               do_echo (msg, params);
+       else {
+               soup_xmlrpc_set_fault (msg, SOUP_XMLRPC_FAULT_SERVER_ERROR_REQUESTED_METHOD_NOT_FOUND,
+                                      "Unknown method %s", method_name);
+       }
+
+       g_free (method_name);
+       g_value_array_free (params);
+}
+
+static void
+xmlrpc_test_exited (GPid pid, int status, gpointer data)
+{
+       errors = WIFEXITED (status) ? WEXITSTATUS (status) : 1;
+       g_main_loop_quit (loop);
+}
+
+static gboolean
+xmlrpc_test_print (GIOChannel *io, GIOCondition cond, gpointer data)
+{
+       char *line;
+       gsize len;
+       GIOStatus status;
+
+       if (!(cond & G_IO_IN))
+               return FALSE;
+
+       status = g_io_channel_read_line (io, &line, &len, NULL, NULL);
+       if (status == G_IO_STATUS_NORMAL) {
+               /* Don't print the exit status, just the debug stuff */
+               if (strncmp (line, "xmlrpc-test:", strlen ("xmlrpc-test:")) != 0)
+                       printf ("%s", line);
+               g_free (line);
+               return TRUE;
+       } else if (status == G_IO_STATUS_AGAIN)
+               return TRUE;
+       else
+               return FALSE;
+}
+
+static void
+do_xmlrpc_tests (SoupURI *uri)
+{
+       char *argv[7];
+       int arg, out;
+       gboolean ok;
+       GPid pid;
+       GError *error = NULL;
+       GIOChannel *child_out;
+
+       argv[0] = "./xmlrpc-test";
+       argv[1] = "-u";
+       argv[2] = soup_uri_to_string (uri, FALSE);
+
+       for (arg = 0; arg < debug_level && arg < 3; arg++)
+               argv[arg + 3] = "-d";
+       argv[arg + 3] = NULL;
+
+       ok = g_spawn_async_with_pipes (NULL, argv, NULL,
+                                      G_SPAWN_DO_NOT_REAP_CHILD,
+                                      NULL, NULL, &pid,
+                                      NULL, &out, NULL,
+                                      &error);
+       g_free (argv[2]);
+
+       if (!ok) {
+               printf ("Could not run xmlrpc-test: %s\n", error->message);
+               errors++;
+               return;
+       }
+
+       g_child_watch_add (pid, xmlrpc_test_exited, NULL);
+       child_out = g_io_channel_unix_new (out);
+       g_io_add_watch (child_out, G_IO_IN | G_IO_ERR | G_IO_HUP,
+                       xmlrpc_test_print, NULL);
+       g_io_channel_unref (child_out);
+}
+
+gboolean run_tests = TRUE;
+
+static GOptionEntry no_test_entry[] = {
+        { "no-tests", 'n', G_OPTION_FLAG_NO_ARG | G_OPTION_FLAG_REVERSE,
+          G_OPTION_ARG_NONE, &run_tests,
+          "Don't run tests, just run the test server", NULL },
+        { NULL }
+};
+
+int
+main (int argc, char **argv)
+{
+       SoupServer *server;
+       SoupURI *uri;
+
+       test_init (argc, argv, no_test_entry);
+
+       server = soup_test_server_new (FALSE);
+       soup_server_add_handler (server, "/xmlrpc-server.php",
+                                server_callback, NULL, NULL);
+
+       loop = g_main_loop_new (NULL, TRUE);
+
+       if (run_tests) {
+               uri = soup_uri_new ("http://localhost/xmlrpc-server.php");
+               soup_uri_set_port (uri, soup_server_get_port (server));
+               do_xmlrpc_tests (uri);
+               soup_uri_free (uri);
+       } else
+               printf ("Listening on port %d\n", soup_server_get_port (server));
+
+       g_main_loop_run (loop);
+       g_main_loop_unref (loop);
+
+       if (run_tests)
+               test_cleanup ();
+       return errors != 0;
+}
index 19d4405..2e3dd38 100644 (file)
@@ -1,9 +1,26 @@
 <?php
 
+function paramfault ()
+{
+       # xmlrpc-epi-php translates this into a real <fault>
+       $fault["faultCode"] = -32602;
+       $fault["faultString"] = "bad parameter";
+       return $fault;
+}
+
+# We only check the params in sum(), because that's the one that
+# xmlrpc-test tests will fail if given bad args
+
 function sum ($method_name, $params, $app_data)
 {
+       if (xmlrpc_get_type ($params[0]) != "array")
+               return paramfault();
+
        $sum = 0;
        foreach ($params[0] as $val) {
+               if (xmlrpc_get_type ($val) != "int")
+                       return paramfault();
+                       
                $sum = $sum + $val;
        }
        return $sum;
@@ -13,9 +30,6 @@ function countBools ($method_name, $params, $app_data)
 {
        $counts["true"] = $counts["false"] = 0;
        foreach ($params[0] as $val) {
-               if (xmlrpc_get_type ($val) != "boolean")
-                       return "bad value: $val";
-
                if ($val)
                        $counts["true"] = $counts["true"] + 1;
                else
@@ -33,10 +47,10 @@ function md5sum ($method_name, $params, $app_data)
 
 function dateChange ($method_name, $params, $app_data)
 {
-       $date_str = $params[0]["date"]->scalar;
+       $date_str = $params[0]->scalar;
        $date = strptime ($date_str, "%Y%m%dT%H:%M:%S");
 
-       foreach ($params[0] as $name => $val) {
+       foreach ($params[1] as $name => $val) {
                if ($name == "date")
                        continue;
                $date[$name] = $val;
index 8686b71..53f6b6b 100644 (file)
@@ -13,7 +13,8 @@
 #include "test-utils.h"
 
 SoupSession *session;
-static const char *uri = "http://localhost:47524/xmlrpc-server.php";
+static const char *default_uri = "http://localhost:47524/xmlrpc-server.php";
+const char *uri = NULL;
 
 static const char *const value_type[] = {
        "BAD",
@@ -51,13 +52,6 @@ do_xmlrpc (const char *method, GValue *retval, ...)
                                  body, strlen (body));
        soup_session_send_message (session, msg);
 
-       if (debug_level >= 3) {
-               debug_printf (3, "\n%s\n%d %s\n%s\n",
-                             msg->request_body->data,
-                             msg->status_code, msg->reason_phrase,
-                             msg->response_body->data);
-       }
-
        if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
                debug_printf (1, "ERROR: %d %s\n", msg->status_code,
                              msg->reason_phrase);
@@ -231,9 +225,7 @@ test_dateChange (void)
        GValue retval;
        gboolean ok;
 
-       debug_printf (1, "dateChange (struct of time and ints -> time): ");
-
-       structval = soup_value_hash_new ();
+       debug_printf (1, "dateChange (date, struct of ints -> time): ");
 
        date = soup_date_new (1970 + (rand () % 50),
                              1 + rand () % 12,
@@ -241,54 +233,55 @@ test_dateChange (void)
                              rand () % 24,
                              rand () % 60,
                              rand () % 60);
-       soup_value_hash_insert (structval, "date", SOUP_TYPE_DATE, date);
-
        if (debug_level >= 2) {
                timestamp = soup_date_to_string (date, SOUP_DATE_ISO8601_XMLRPC);
-               debug_printf (2, "{ date: %s", timestamp);
+               debug_printf (2, "date: %s, {", timestamp);
                g_free (timestamp);
        }
 
+       structval = soup_value_hash_new ();
+
        if (rand () % 3) {
                date->year = 1970 + (rand () % 50);
-               debug_printf (2, ", tm_year: %d", date->year - 1900);
+               debug_printf (2, "tm_year: %d, ", date->year - 1900);
                soup_value_hash_insert (structval, "tm_year",
                                        G_TYPE_INT, date->year - 1900);
        }
        if (rand () % 3) {
                date->month = 1 + rand () % 12;
-               debug_printf (2, ", tm_mon: %d", date->month - 1);
+               debug_printf (2, "tm_mon: %d, ", date->month - 1);
                soup_value_hash_insert (structval, "tm_mon",
                                        G_TYPE_INT, date->month - 1);
        }
        if (rand () % 3) {
                date->day = 1 + rand () % 28;
-               debug_printf (2, ", tm_mday: %d", date->day);
+               debug_printf (2, "tm_mday: %d, ", date->day);
                soup_value_hash_insert (structval, "tm_mday",
                                        G_TYPE_INT, date->day);
        }
        if (rand () % 3) {
                date->hour = rand () % 24;
-               debug_printf (2, ", tm_hour: %d", date->hour);
+               debug_printf (2, "tm_hour: %d, ", date->hour);
                soup_value_hash_insert (structval, "tm_hour",
                                        G_TYPE_INT, date->hour);
        }
        if (rand () % 3) {
                date->minute = rand () % 60;
-               debug_printf (2, ", tm_min: %d", date->minute);
+               debug_printf (2, "tm_min: %d, ", date->minute);
                soup_value_hash_insert (structval, "tm_min",
                                        G_TYPE_INT, date->minute);
        }
        if (rand () % 3) {
                date->second = rand () % 60;
-               debug_printf (2, ", tm_sec: %d", date->second);
+               debug_printf (2, "tm_sec: %d, ", date->second);
                soup_value_hash_insert (structval, "tm_sec",
                                        G_TYPE_INT, date->second);
        }
 
-       debug_printf (2, " } -> ");
+       debug_printf (2, "} -> ");
 
        ok = (do_xmlrpc ("dateChange", &retval,
+                        SOUP_TYPE_DATE, date,
                         G_TYPE_HASH_TABLE, structval,
                         G_TYPE_INVALID) &&
              check_xmlrpc (&retval, SOUP_TYPE_DATE, &result));
@@ -377,11 +370,82 @@ test_echo (void)
        return TRUE;
 }
 
+static gboolean
+do_bad_xmlrpc (const char *body)
+{
+       SoupMessage *msg;
+       GError *err = NULL;
+       GValue retval;
+
+       msg = soup_message_new ("POST", uri);
+       soup_message_set_request (msg, "text/xml", SOUP_MEMORY_COPY,
+                                 body, strlen (body));
+       soup_session_send_message (session, msg);
+
+       if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
+               debug_printf (1, "ERROR: %d %s\n", msg->status_code,
+                             msg->reason_phrase);
+               g_object_unref (msg);
+               return FALSE;
+       }
+
+       if (!soup_xmlrpc_parse_method_response (msg->response_body->data,
+                                               msg->response_body->length,
+                                               &retval, &err)) {
+               if (err) {
+                       debug_printf (1, "FAULT: %d %s (OK!)\n",
+                                     err->code, err->message);
+                       g_error_free (err);
+                       g_object_unref (msg);
+                       return TRUE;
+               } else
+                       debug_printf (1, "ERROR: could not parse response\n");
+       } else
+               debug_printf (1, "Unexpectedly got successful response!\n");
+
+       g_object_unref (msg);
+       return FALSE;
+}
+
+static gboolean
+test_fault_malformed (void)
+{
+       debug_printf (1, "malformed request: ");
+
+       return do_bad_xmlrpc ("<methodCall/>");
+}
+
+static gboolean
+test_fault_method (void)
+{
+       debug_printf (1, "request to non-existent method: ");
+
+       return do_bad_xmlrpc ("<methodCall><methodName>no_such_method</methodName><params><param><value><int>1</int></value></param></params></methodCall>");
+}
+
+static gboolean
+test_fault_args (void)
+{
+       debug_printf (1, "request with invalid args: ");
+
+       return do_bad_xmlrpc ("<methodCall><methodName>sum</methodName><params><param><value><int>1</int></value></param></params></methodCall>");
+}
+
+static GOptionEntry uri_entry[] = {
+        { "uri", 'u', 0, G_OPTION_ARG_STRING, &uri,
+          "Alternate URI for server", NULL },
+        { NULL }
+};
+
 int
 main (int argc, char **argv)
 {
-       test_init (argc, argv, NULL);
-       apache_init ();
+       test_init (argc, argv, uri_entry);
+
+       if (!uri) {
+               apache_init ();
+               uri = default_uri;
+       }
 
        srand (time (NULL));
 
@@ -397,10 +461,16 @@ main (int argc, char **argv)
                errors++;
        if (!test_echo ())
                errors++;
+       if (!test_fault_malformed ())
+               errors++;
+       if (!test_fault_method ())
+               errors++;
+       if (!test_fault_args ())
+               errors++;
 
        soup_session_abort (session);
        g_object_unref (session);
 
        test_cleanup ();
-       return errors = 0;
+       return errors != 0;
 }