initial load of upstream version 1.06.32
[xmlrpc-c] / lib / libwww_transport / xmlrpc_libwww_transport.c
diff --git a/lib/libwww_transport/xmlrpc_libwww_transport.c b/lib/libwww_transport/xmlrpc_libwww_transport.c
new file mode 100644 (file)
index 0000000..61d16ac
--- /dev/null
@@ -0,0 +1,937 @@
+/* Copyright (C) 2001 by First Peer, Inc. All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+** 1. Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+** 2. Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in the
+**    documentation and/or other materials provided with the distribution.
+** 3. The name of the author may not be used to endorse or promote products
+**    derived from this software without specific prior written permission. 
+**  
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+** SUCH DAMAGE. */
+
+#include "xmlrpc_config.h"
+
+#include <stddef.h>
+
+#include "bool.h"
+#include "mallocvar.h"
+
+#include "xmlrpc-c/base.h"
+#include "xmlrpc-c/base_int.h"
+#include "xmlrpc-c/string_int.h"
+#include "xmlrpc-c/client.h"
+#include "xmlrpc-c/client_int.h"
+
+/* The libwww interface */
+
+/* These headers mistakenly define the macro PACKAGE.  As
+   xmlrpc_config.h already defines PACKAGE according to the package we're
+   actually part of, this causes a conflict.  So we undef here and then
+   to avoid possible problems with an incorrect PACKAGE, we undef it again
+   after.
+*/
+#undef PACKAGE
+#include "WWWLib.h"
+#include "WWWHTTP.h"
+#include "WWWInit.h"
+#undef PACKAGE
+
+/* Include our libwww SSL headers, if available. */
+#if HAVE_LIBWWW_SSL
+#include "WWWSSL.h"
+#endif
+
+#include "xmlrpc_libwww_transport.h"
+
+/* This value was discovered by Rick Blair. His efforts shaved two seconds
+** off of every request processed. Many thanks. */
+#define SMALLEST_LEGAL_LIBWWW_TIMEOUT (21)
+
+#define XMLRPC_CLIENT_USE_TIMEOUT   (2)
+
+
+struct xmlrpc_client_transport {
+    int saved_flags;
+    HTList *xmlrpc_conversions;
+    void * cookieJarP;
+        /* This is a collection of all the cookies that servers have set
+           via responses to prior requests.  It's not implemented today.
+        */
+    bool tracingOn;
+};
+
+static struct xmlrpc_client_transport clientTransport;
+
+
+typedef struct {
+/*----------------------------------------------------------------------------
+   This object represents one RPC.
+-----------------------------------------------------------------------------*/
+    struct xmlrpc_client_transport * clientTransportP;
+
+    /* These fields are used when performing synchronous calls. */
+    bool is_done;
+    int http_status;
+
+    /* Low-level information used by libwww. */
+    HTRequest *      request;
+    HTChunk *        response_data;
+    HTParentAnchor * source_anchor;
+    HTAnchor *       dest_anchor;
+
+    xmlrpc_transport_asynch_complete complete;
+    struct xmlrpc_call_info * callInfoP; 
+} rpc;
+
+
+
+static void
+createCookieJar(xmlrpc_env * const envP ATTR_UNUSED,
+                void **      const cookieJarP ATTR_UNUSED) {
+
+    /* Cookies not implemented yet */
+}
+
+
+
+static void
+destroyCookieJar(void * cookieJarP ATTR_UNUSED) {
+
+    /* Cookies not implemented yet */
+}
+
+
+
+static void
+initLibwww(const char * const appname,
+           const char * const appversion) {
+    
+    /* We initialize the library using a robot profile, because we don't
+    ** care about redirects or HTTP authentication, and we want to
+    ** reduce our application footprint as much as possible. */
+    HTProfile_newRobot(appname, appversion);
+
+    /* Ilya Goldberg <igg@mit.edu> provided the following code to access
+    ** SSL-protected servers. */
+#if HAVE_LIBWWW_SSL
+    /* Set the SSL protocol method. By default, it is the highest
+    ** available protocol. Setting it up to SSL_V23 allows the client
+    ** to negotiate with the server and set up either TSLv1, SSLv3,
+    ** or SSLv2 */
+    HTSSL_protMethod_set(HTSSL_V23);
+    
+    /* Set the certificate verification depth to 2 in order to be able to
+    ** validate self-signed certificates */
+    HTSSL_verifyDepth_set(2);
+    
+    /* Register SSL stuff for handling ssl access. The parameter we pass
+    ** is NO because we can't be pre-emptive with POST */
+    HTSSLhttps_init(NO);
+#endif /* HAVE_LIBWWW_SSL */
+    
+    /* For interoperability with Frontier, we need to tell libwww *not*
+    ** to send 'Expect: 100-continue' headers. But if we're not sending
+    ** these, we shouldn't wait for them. So set our built-in delays to
+    ** the smallest legal values. */
+    HTTP_setBodyWriteDelay (SMALLEST_LEGAL_LIBWWW_TIMEOUT,
+                            SMALLEST_LEGAL_LIBWWW_TIMEOUT);
+    
+    /* We attempt to disable all of libwww's chatty, interactive
+    ** prompts. Let's hope this works. */
+    HTAlert_setInteractive(NO);
+    
+    /* Here are some alternate setup calls which will help greatly
+    ** with debugging, should the need arise.
+    **
+    ** HTProfile_newNoCacheClient(appname, appversion);
+    ** HTAlert_setInteractive(YES);
+    ** HTPrint_setCallback(printer);
+    ** HTTrace_setCallback(tracer); */
+}
+
+
+
+static void 
+create(xmlrpc_env *                      const envP,
+       int                               const flags,
+       const char *                      const appname,
+       const char *                      const appversion,
+       const struct xmlrpc_xportparms *  const transportParmsP ATTR_UNUSED,
+       size_t                            const parm_size ATTR_UNUSED,
+       struct xmlrpc_client_transport ** const handlePP) {
+/*----------------------------------------------------------------------------
+   This does the 'create' operation for a Libwww client transport.
+-----------------------------------------------------------------------------*/
+    /* The Libwww transport is not re-entrant -- you can have only one
+       per program instance.  Even if we changed the Xmlrpc-c code not
+       to use global variables, that wouldn't help because Libwww
+       itself is not re-entrant.  
+       
+       So we use a global variable ('clientTransport') for our transport state.
+    */
+    struct xmlrpc_client_transport * const clientTransportP = &clientTransport;
+    *handlePP = clientTransportP;
+
+    clientTransportP->saved_flags = flags;
+
+    createCookieJar(envP, &clientTransportP->cookieJarP);
+    if (!envP->fault_occurred) {
+        if (!(clientTransportP->saved_flags & 
+              XMLRPC_CLIENT_SKIP_LIBWWW_INIT))
+            initLibwww(appname, appversion);
+
+        /* Set up our list of conversions for XML-RPC requests. This is a
+        ** massively stripped-down version of the list in libwww's HTInit.c.
+        ** XXX - This is hackish; 10.0 is an arbitrary, large quality factor
+        ** designed to override the built-in converter for XML. */
+        clientTransportP->xmlrpc_conversions = HTList_new();
+        HTConversion_add(clientTransportP->xmlrpc_conversions, 
+                         "text/xml", "*/*",
+                         HTThroughLine, 10.0, 0.0, 0.0);
+
+        if (envP->fault_occurred)
+            destroyCookieJar(clientTransportP->cookieJarP);
+    }
+    if (getenv("XMLRPC_LIBWWW_TRACE"))
+        clientTransportP->tracingOn = TRUE;
+    else
+        clientTransportP->tracingOn = FALSE;
+}
+
+
+
+static void 
+destroy(struct xmlrpc_client_transport * const clientTransportP) {
+/*----------------------------------------------------------------------------
+   This does the 'destroy' operation for a Libwww client transport.
+-----------------------------------------------------------------------------*/
+    XMLRPC_ASSERT(clientTransportP != NULL);
+
+    if (!(clientTransportP->saved_flags & XMLRPC_CLIENT_SKIP_LIBWWW_INIT)) {
+        HTProfile_delete();
+    }
+    destroyCookieJar(clientTransportP->cookieJarP);
+}
+
+
+
+/*=========================================================================
+**  HTTP Error Reporting
+**=======================================================================*/
+
+static void
+formatLibwwwError(HTRequest *   const requestP,
+                  const char ** const msgP) {
+/*----------------------------------------------------------------------------
+   When something fails in a Libwww request, Libwww generates a stack
+   of error information (precious little information, of course, in the
+   Unix tradition) and attaches it to the request object.  We make a message
+   out of that information.
+
+   We rely on Libwww's HTDialog_errorMessage() to do the bulk of the
+   formatting; we might be able to coax more information out of the request
+   if we interpreted the error stack directly.
+-----------------------------------------------------------------------------*/
+    HTList * const errStack = HTRequest_error(requestP);
+    
+    if (errStack == NULL)
+        xmlrpc_asprintf(msgP, "Libwww supplied no error details");
+    else {
+        /* Get an error message from libwww.  The middle three
+           parameters to HTDialog_errorMessage appear to be ignored.
+           XXX - The documentation for this API is terrible, so we may
+           be using it incorrectly.  
+        */
+        const char * msg =
+            HTDialog_errorMessage(requestP, HT_A_MESSAGE, HT_MSG_NULL,
+                                  "An error occurred", errStack);
+        
+        if (msg == NULL)
+            xmlrpc_asprintf(msgP, "Libwww supplied some error detail, "
+                            "but its HTDialog_errorMessage() subroutine "
+                            "mysteriously failed to interpret it for us.");
+        else
+            *msgP = msg;
+    }
+}
+
+
+
+static void 
+set_fault_from_http_request(xmlrpc_env * const envP,
+                            int          const status,
+                            HTRequest *  const requestP) {
+/*----------------------------------------------------------------------------
+   Assuming 'requestP' identifies a completed libwww HTTP request, set
+   *envP according to its success/error status.
+-----------------------------------------------------------------------------*/
+    XMLRPC_ASSERT_PTR_OK(requestP);
+
+    if (status == 200) {
+        /* No error.  Don't set one in *envP */
+    } else {
+        const char * libwwwMsg;
+        formatLibwwwError(requestP, &libwwwMsg);
+
+        if (status == -1)
+            xmlrpc_env_set_fault_formatted(
+                envP, XMLRPC_NETWORK_ERROR,
+                "Unable to complete the HTTP request.  %s", libwwwMsg);
+        else {
+            xmlrpc_env_set_fault_formatted(
+                envP, XMLRPC_NETWORK_ERROR,
+                "HTTP request completed with HTTp error %d.  %s",
+                status, libwwwMsg);
+        }
+        xmlrpc_strfree(libwwwMsg);
+    }
+}
+
+
+
+static BOOL 
+setCookie(HTRequest * const request,
+          HTCookie *  const cookieP ATTR_UNUSED,
+          void *      const param ATTR_UNUSED) {
+/*----------------------------------------------------------------------------
+  This is the callback from libwww to tell us the server (according to
+  its response) wants us to store a cookie (and include it in future
+  requests).
+
+  We assume that the cookies "domain" is the server's host name
+  (there are options on the libwww connection to make libwww call this
+  callback only when that's the case).
+-----------------------------------------------------------------------------*/
+    rpc * const rpcP = HTRequest_context(request);
+    struct xmlrpc_client_transport * const clientTransportP = 
+        rpcP->clientTransportP;
+
+    BOOL retval;
+
+    /* Avoid unused variable warning */
+    if (clientTransportP->cookieJarP == clientTransportP->cookieJarP) {}
+    /* Cookies are not implemented today */
+    retval = NO;
+
+    return retval;
+}
+
+
+
+static HTAssocList *
+cookiesForHost(const char * const host ATTR_UNUSED,
+               void *       const cookieJarP ATTR_UNUSED) {
+/*----------------------------------------------------------------------------
+  Find and return all the cookies in jar 'cookieJarP' that are for the
+  host 'host'.
+-----------------------------------------------------------------------------*/
+    HTAssocList * hisCookiesP;
+
+    hisCookiesP = HTAssocList_new();
+
+    if (hisCookiesP) {
+        /* Cookies are not implemented yet */
+        /* Library/Examples/cookie.c in the w3c-libwww source tree contains
+           an example of constructing the cookie list we are supposed to
+           return.  But today, we return an empty list.
+        */
+    }
+    return hisCookiesP;
+}
+
+
+
+static HTAssocList *
+findCookie(HTRequest * const request,
+           void *      const param ATTR_UNUSED) {
+/*----------------------------------------------------------------------------
+  This is the callback from libwww to get the cookies to include in a
+  request (presumably values the server set via a prior response).
+-----------------------------------------------------------------------------*/
+    rpc * const rpcP = HTRequest_context(request);
+    struct xmlrpc_client_transport * const clientTransportP = 
+        rpcP->clientTransportP;
+    const char * const addr = 
+        HTAnchor_address((HTAnchor *) HTRequest_anchor(request));
+    const char * const host = HTParse(addr, "", PARSE_HOST);
+
+    return cookiesForHost(host, clientTransportP->cookieJarP);
+}
+
+
+
+static void 
+deleteSourceAnchor(HTParentAnchor * const anchor) {
+
+    /* We need to clear the document first, or else libwww won't
+    ** really delete the anchor. */
+    HTAnchor_setDocument(anchor, NULL);
+
+    /* XXX - Deleting this anchor causes HTLibTerminate to dump core. */
+    /* HTAnchor_delete(anchor); */
+}
+
+
+
+static void
+createSourceAnchor(xmlrpc_env *       const envP,
+                   HTParentAnchor **  const sourceAnchorPP,
+                   xmlrpc_mem_block * const xmlP) {
+
+    HTParentAnchor * const sourceAnchorP = HTTmpAnchor(NULL);
+
+    if (sourceAnchorP == NULL)
+        xmlrpc_env_set_fault_formatted(
+            envP, XMLRPC_INTERNAL_ERROR, 
+            "Unable to build source anchor.  HTTmpAnchor() failed.");
+    else {
+        HTAnchor_setDocument(sourceAnchorP,
+                             XMLRPC_MEMBLOCK_CONTENTS(char, xmlP));
+        HTAnchor_setFormat(sourceAnchorP, HTAtom_for("text/xml"));
+        HTAnchor_setLength(sourceAnchorP, XMLRPC_MEMBLOCK_SIZE(char, xmlP));
+
+        *sourceAnchorPP = sourceAnchorP;
+    }
+}
+
+
+
+static void
+createDestAnchor(xmlrpc_env *               const envP,
+                 HTAnchor **                const destAnchorPP,
+                 const xmlrpc_server_info * const serverP) {
+                 
+    *destAnchorPP = HTAnchor_findAddress(serverP->_server_url);
+
+    if (*destAnchorPP == NULL)
+        xmlrpc_env_set_fault_formatted(
+            envP, XMLRPC_INTERNAL_ERROR,
+            "Could not build destination anchor.  HTAnchor_findAddress() "
+            "failed.");
+}
+
+
+
+static void
+rpcCreate(xmlrpc_env *                       const envP,
+          struct xmlrpc_client_transport *   const clientTransportP,
+          const xmlrpc_server_info *         const serverP,
+          xmlrpc_mem_block *                 const xmlP,
+          xmlrpc_transport_asynch_complete         complete, 
+          struct xmlrpc_call_info *          const callInfoP,
+          rpc **                             const rpcPP) {
+
+    rpc *rpcP;
+    HTRqHd request_headers;
+    HTStream *target_stream;
+
+    /* Allocate our structure. */
+    MALLOCVAR(rpcP);
+    XMLRPC_FAIL_IF_NULL(rpcP, envP, XMLRPC_INTERNAL_ERROR,
+                        "Out of memory in rpcCreate()");
+
+    /* Set up our basic members. */
+    rpcP->clientTransportP = clientTransportP;
+    rpcP->is_done = FALSE;
+    rpcP->http_status = 0;
+    rpcP->complete = complete;
+    rpcP->callInfoP = callInfoP;
+
+    /* Start cookie handler. */
+    HTCookie_init();
+    HTCookie_setCallbacks(setCookie, NULL, findCookie, NULL);
+    HTCookie_setCookieMode(HT_COOKIE_ACCEPT | 
+                           HT_COOKIE_SEND | 
+                           HT_COOKIE_SAME_HOST);
+
+    /* Cookies aren't implemented today; reset. */
+    HTCookie_setCookieMode(0);
+    
+    /* Create a HTRequest object. */
+    rpcP->request = HTRequest_new();
+    XMLRPC_FAIL_IF_NULL(rpcP, envP, XMLRPC_INTERNAL_ERROR,
+                        "HTRequest_new failed");
+    
+    /* Install ourselves as the request context. */
+    HTRequest_setContext(rpcP->request, rpcP);
+
+    /* XXX - Disable the 'Expect:' header so we can talk to Frontier. */
+    request_headers = HTRequest_rqHd(rpcP->request);
+    request_headers = request_headers & ~HT_C_EXPECT;
+    HTRequest_setRqHd(rpcP->request, request_headers);
+
+    /* Send an authorization header if we need one. */
+    if (serverP->_http_basic_auth)
+        HTRequest_addCredentials(rpcP->request, "Authorization",
+                                 serverP->_http_basic_auth);
+    
+    /* Make sure there is no XML conversion handler to steal our data.
+    ** The 'override' parameter is currently ignored by libwww, so our
+    ** list of conversions must be designed to co-exist with the built-in
+    ** conversions. */
+    HTRequest_setConversion(rpcP->request, 
+                            clientTransportP->xmlrpc_conversions, NO);
+
+    /* Set up our response buffer. */
+    target_stream = HTStreamToChunk(rpcP->request, &rpcP->response_data, 0);
+    XMLRPC_FAIL_IF_NULL(rpcP->response_data, envP, XMLRPC_INTERNAL_ERROR,
+                        "HTStreamToChunk failed");
+    XMLRPC_ASSERT(target_stream != NULL);
+    HTRequest_setOutputStream(rpcP->request, target_stream);
+    HTRequest_setOutputFormat(rpcP->request, WWW_SOURCE);
+
+    createSourceAnchor(envP, &rpcP->source_anchor, xmlP);
+
+    if (!envP->fault_occurred) {
+        createDestAnchor(envP, &rpcP->dest_anchor, serverP);
+
+        if (envP->fault_occurred)
+            /* See below for comments about deleting the source and dest
+            ** anchors. This is a bit of a black art. */
+            deleteSourceAnchor(rpcP->source_anchor);
+    }
+    
+ cleanup:
+    if (envP->fault_occurred) {
+        if (rpcP) {
+            if (rpcP->request)
+                HTRequest_delete(rpcP->request);
+            if (rpcP->response_data)
+                HTChunk_delete(rpcP->response_data);
+            free(rpcP);
+        }
+    }
+    *rpcPP = rpcP;
+}
+
+
+
+static void 
+rpcDestroy(rpc * const rpcP) {
+
+    XMLRPC_ASSERT_PTR_OK(rpcP);
+    XMLRPC_ASSERT(rpcP->request != XMLRPC_BAD_POINTER);
+    XMLRPC_ASSERT(rpcP->response_data != XMLRPC_BAD_POINTER);
+
+    /* Junji Kanemaru reports on 05.04.11 that with asynch calls, he
+       get a segfault, and reversing the order of deleting the request
+       and the response chunk buffer cured it.  But we find no reason
+       that should be so, so we're waiting for someone to arrive at an
+       explanation before changing anything.  HTRequest_delete() does
+       destroy the output stream, and the output stream refers to the
+       response chunk, but HTRequest_delete() explicitly refrains from
+       destroying the response chunk.  And the response chunk does not
+       refer to the request.
+    */
+
+    HTRequest_delete(rpcP->request);
+    rpcP->request = XMLRPC_BAD_POINTER;
+    HTChunk_delete(rpcP->response_data);
+    rpcP->response_data = XMLRPC_BAD_POINTER;
+
+    /* This anchor points to private data, so we're allowed to delete it.  */
+    deleteSourceAnchor(rpcP->source_anchor);
+
+    /* WARNING: We can't delete the destination anchor, because this points
+    ** to something in the outside world, and lives in a libwww hash table.
+    ** Under certain circumstances, this anchor may have been reissued to
+    ** somebody else. So over time, the anchor cache will grow. If this
+    ** is a problem for your application, read the documentation for
+    ** HTAnchor_deleteAll.
+    **
+    ** However, we CAN check to make sure that no documents have been
+    ** attached to the anchor. This assertion may fail if you're using
+    ** libwww for something else, so please feel free to comment it out. */
+    /* XMLRPC_ASSERT(HTAnchor_document(rpcP->dest_anchor) == NULL);
+     */
+
+    HTCookie_deleteCallbacks();
+    HTCookie_terminate();
+
+    free(rpcP);
+}
+
+
+
+static void
+extract_response_chunk(xmlrpc_env *        const envP,
+                       rpc *               const rpcP,
+                       xmlrpc_mem_block ** const responseXmlPP) {
+
+    /* Check to make sure that w3c-libwww actually sent us some data.
+    ** XXX - This may happen if libwww is shut down prematurely, believe it
+    ** or not--we'll get a 200 OK and no data. Gag me with a bogus design
+    ** decision. This may also fail if some naughty libwww converter
+    ** ate our data unexpectedly. */
+    if (!HTChunk_data(rpcP->response_data))
+        xmlrpc_env_set_fault(envP, XMLRPC_NETWORK_ERROR,
+                             "w3c-libwww returned no data");
+    else {
+        *responseXmlPP = XMLRPC_MEMBLOCK_NEW(char, envP, 0);
+        if (!envP->fault_occurred) {
+            if (rpcP->clientTransportP->tracingOn) {
+                fprintf(stderr, "HTTP chunk received: %u bytes: '%.*s'",
+                        HTChunk_size(rpcP->response_data),
+                        HTChunk_size(rpcP->response_data),
+                        HTChunk_data(rpcP->response_data));
+            }
+
+            XMLRPC_MEMBLOCK_APPEND(char, envP, *responseXmlPP,
+                                   HTChunk_data(rpcP->response_data),
+                                   HTChunk_size(rpcP->response_data));
+            if (envP->fault_occurred)
+                XMLRPC_MEMBLOCK_FREE(char, *responseXmlPP);
+        }
+    }
+}
+
+
+
+static int 
+synch_terminate_handler(HTRequest *  const request,
+                        HTResponse * const response ATTR_UNUSED,
+                        void *       const param ATTR_UNUSED,
+                        int          const status) {
+/*----------------------------------------------------------------------------
+   This is a libwww request completion handler.
+
+   HTEventList_newLoop() calls this when it completes a request (with this
+   registered as the completion handler).
+-----------------------------------------------------------------------------*/
+    rpc *rpcP;
+
+    rpcP = HTRequest_context(request);
+
+    rpcP->is_done = TRUE;
+    rpcP->http_status = status;
+
+    HTEventList_stopLoop();
+
+    return HT_OK;
+}
+
+
+
+static void
+call(xmlrpc_env *                     const envP,
+     struct xmlrpc_client_transport * const clientTransportP,
+     const xmlrpc_server_info *       const serverP,
+     xmlrpc_mem_block *               const xmlP,
+     xmlrpc_mem_block **              const responsePP) {
+/*----------------------------------------------------------------------------
+   This does the 'call' operation for a Libwww client transport.
+-----------------------------------------------------------------------------*/
+    rpc * rpcP;
+
+    XMLRPC_ASSERT_ENV_OK(envP);
+    XMLRPC_ASSERT_PTR_OK(serverP);
+    XMLRPC_ASSERT_PTR_OK(xmlP);
+    XMLRPC_ASSERT_PTR_OK(responsePP);
+
+    rpcCreate(envP, clientTransportP, serverP, xmlP, NULL, NULL, &rpcP);
+    if (!envP->fault_occurred) {
+        int ok;
+        
+        /* Install our request handler. */
+        HTRequest_addAfter(rpcP->request, &synch_terminate_handler, 
+                           NULL, NULL, HT_ALL, HT_FILTER_LAST, NO);
+
+        /* Start our request running. */
+        ok = HTPostAnchor(rpcP->source_anchor, 
+                          rpcP->dest_anchor, 
+                          rpcP->request);
+        if (!ok)
+            xmlrpc_env_set_fault(
+                envP, XMLRPC_NETWORK_ERROR,
+                "Libwww HTPostAnchor() failed to start POST request");
+        else {
+            /* Run our event-processing loop.  HTEventList_newLoop()
+               is what calls synch_terminate_handler(), by virtue of
+               it being registered as a handler.  It may return for
+               other reasons than the request being complete, though.
+               so we call it in a loop until synch_terminate_handler()
+               really has been called.
+            */
+            while (!rpcP->is_done)
+                HTEventList_newLoop();
+        
+            /* Fail if we didn't get a "200 OK" response from the server */
+            if (rpcP->http_status != 200)
+                set_fault_from_http_request(
+                    envP, rpcP->http_status, 
+                    rpcP->request);
+            else {
+                /* XXX - Check to make sure response type is text/xml here. */
+                
+                extract_response_chunk(envP, rpcP, responsePP);
+            }
+        }
+        rpcDestroy(rpcP);
+    }
+}
+
+
+
+/*=========================================================================
+**  Event Loop
+**=========================================================================
+**  We manage a fair bit of internal state about our event loop. This is
+**  needed to determine when (and if) we should exit the loop.
+*/
+
+static int outstanding_asynch_calls = 0;
+static int event_loop_flags = 0;
+static int timer_called = 0;
+
+static void 
+register_asynch_call(void) {
+    XMLRPC_ASSERT(outstanding_asynch_calls >= 0);
+    outstanding_asynch_calls++;
+}
+
+
+
+static void 
+unregister_asynch_call(void) {
+
+    XMLRPC_ASSERT(outstanding_asynch_calls > 0);
+    outstanding_asynch_calls--;
+    if (outstanding_asynch_calls == 0)
+        HTEventList_stopLoop();
+}
+
+
+
+static int 
+timer_callback(HTTimer *   const timer     ATTR_UNUSED,
+               void *      const user_data ATTR_UNUSED,
+               HTEventType const event     ATTR_UNUSED) {
+/*----------------------------------------------------------------------------
+  A handy timer callback which cancels the running event loop. 
+-----------------------------------------------------------------------------*/
+    XMLRPC_ASSERT(event == HTEvent_TIMEOUT);
+    timer_called = 1;
+    HTEventList_stopLoop();
+    
+    /* XXX - The meaning of this return value is undocumented, but close
+    ** inspection of libwww's source suggests that we want to return HT_OK. */
+    return HT_OK;
+}
+
+
+
+static void 
+eventLoopRun(int            const flags, 
+             xmlrpc_timeout const milliseconds) {
+/*----------------------------------------------------------------------------
+   Process all responses from outstanding requests as they come in.
+   Return when there are no more outstanding responses.
+
+   Or, if 'flags' has the XMLRPC_CLIENT_USE_TIMEOUT flag set, return
+   when 'milliseconds' milliseconds have elapsed, regardless of whether
+   there are still outstanding responses.
+
+   The processing we do consists of telling libwww to process the
+   completion of the libwww request.  That normally includes calling
+   the xmlrpc_libwww_transport request termination handler, because
+   the submitter of the libwww request would have registered that as a
+   callback.
+-----------------------------------------------------------------------------*/
+    if (outstanding_asynch_calls > 0) {
+        HTTimer *timer;
+
+        event_loop_flags = flags;
+        
+        /* Run an appropriate event loop.  The HTEeventList_newLoop()
+           is what calls asynch_terminate_handler(), by virtue of it
+           being registered as a handler.
+        */
+        if (event_loop_flags & XMLRPC_CLIENT_USE_TIMEOUT) {
+            
+            /* Run our event loop with a timer. Note that we need to be very
+            ** careful about race conditions--timers can be fired in either
+            ** HTimer_new or HTEventList_newLoop. And if our callback were to
+            ** get called before we entered the loop, we would never exit.
+            ** So we use a private flag of our own--we can't even rely on
+            ** HTTimer_hasTimerExpired, because that only checks the time,
+            ** not whether our callback has been run. Yuck. */
+            timer_called = 0;
+            timer = HTTimer_new(NULL, &timer_callback, NULL,
+                                milliseconds, YES, NO);
+            XMLRPC_ASSERT(timer != NULL);
+            if (!timer_called)
+                HTEventList_newLoop();
+            HTTimer_delete(timer);
+            
+        } else {
+            /* Run our event loop without a timer. */
+            HTEventList_newLoop();
+        }
+        
+        /* Reset our flags, so we don't interfere with direct calls to the
+        ** libwww event loop functions. */
+        event_loop_flags = 0;
+    } else {
+        /* There are *no* calls to process.  This may mean that none
+           of the asynch calls were ever set up, and the client's
+           callbacks have already been called with an error, or that
+           all outstanding calls were completed during a previous
+           synchronous call.  
+        */
+    }
+}
+
+
+
+static void 
+finishAsynch(
+    struct xmlrpc_client_transport * const clientTransportP ATTR_UNUSED,
+    xmlrpc_timeoutType               const timeoutType,
+    xmlrpc_timeout                   const timeout) {
+/*----------------------------------------------------------------------------
+   This does the 'finish_asynch' operation for a Libwww client transport.
+-----------------------------------------------------------------------------*/
+    eventLoopRun(timeoutType == timeout_yes ? XMLRPC_CLIENT_USE_TIMEOUT : 0,
+                 timeout);
+}
+
+
+
+static int 
+asynch_terminate_handler(HTRequest *  const request,
+                         HTResponse * const response ATTR_UNUSED,
+                         void *       const param ATTR_UNUSED,
+                         int          const status) {
+/*----------------------------------------------------------------------------
+   Handle the completion of a libwww request.
+
+   This is the bottom half of the xmlrpc_libwww_transport asynchronous
+   call dispatcher.  It's what the dispatcher registers with libwww as
+   a "local after filter" so that libwww calls it when a request that
+   xmlrpc_libwww_transport submitted to it is complete.
+
+   We destroy the RPC, including the request which is our argument.
+   Strange as that may seem, it is apparently legal for an after filter
+   to destroy the request that was passed to it -- or not.
+-----------------------------------------------------------------------------*/
+    xmlrpc_env env;
+    rpc * rpcP;
+    xmlrpc_mem_block * responseXmlP;
+
+    XMLRPC_ASSERT_PTR_OK(request);
+
+    xmlrpc_env_init(&env);
+
+    rpcP = HTRequest_context(request);
+
+    /* Unregister this call from the event loop. Among other things, this
+    ** may decide to stop the event loop.
+    **/
+    unregister_asynch_call();
+
+    /* Give up if an error occurred. */
+    if (status != 200)
+        set_fault_from_http_request(&env, status, request);
+    else {
+        /* XXX - Check to make sure response type is text/xml here. */
+        extract_response_chunk(&env, rpcP, &responseXmlP);
+    }
+    rpcP->complete(rpcP->callInfoP, responseXmlP, env);
+
+    if (!env.fault_occurred)
+        XMLRPC_MEMBLOCK_FREE(char, responseXmlP);
+
+    rpcDestroy(rpcP);
+    
+    xmlrpc_env_clean(&env);
+    return HT_OK;
+}
+
+
+
+static void 
+sendRequest(xmlrpc_env *                     const envP, 
+            struct xmlrpc_client_transport * const clientTransportP,
+            const xmlrpc_server_info *       const serverP,
+            xmlrpc_mem_block *               const xmlP,
+            xmlrpc_transport_asynch_complete       complete,
+            struct xmlrpc_call_info *        const callInfoP) {
+/*----------------------------------------------------------------------------
+   Initiate an XML-RPC rpc asynchronously.  Don't wait for it to go to
+   the server.
+
+   Unless we return failure, we arrange to have complete() called when
+   the rpc completes.
+
+   This does the 'send_request' operation for a Libwww client transport.
+-----------------------------------------------------------------------------*/
+    rpc * rpcP;
+
+    XMLRPC_ASSERT_PTR_OK(envP);
+    XMLRPC_ASSERT_PTR_OK(serverP);
+    XMLRPC_ASSERT_PTR_OK(xmlP);
+    XMLRPC_ASSERT_PTR_OK(callInfoP);
+
+    rpcCreate(envP, clientTransportP, serverP, xmlP, complete, callInfoP, 
+              &rpcP);
+    if (!envP->fault_occurred) {
+        int ok;
+
+        /* Install our request handler. */
+        HTRequest_addAfter(rpcP->request, &asynch_terminate_handler, 
+                           NULL, NULL, HT_ALL, HT_FILTER_LAST, NO);
+
+        /* Register our asynchronous call with the event loop.  This means
+           the user's callback is guaranteed to be called eventually.
+        */
+        register_asynch_call();
+
+        /* This makes the TCP connection and sends the XML to the server
+           as an HTTP POST request.
+
+           There was a comment here that said this might return failure
+           (!ok) and still invoke our completion handler
+           (asynch_terminate_handler().  The code attempted to deal with
+           that.  Well, it's impossible to deal with that, so if it really
+           happens, we must fix Libwww.  -Bryan 04.11.23.
+        */
+
+        ok = HTPostAnchor(rpcP->source_anchor, 
+                          rpcP->dest_anchor, 
+                          rpcP->request);
+        if (!ok) {
+            unregister_asynch_call();
+            xmlrpc_env_set_fault(envP, XMLRPC_NETWORK_ERROR,
+                                 "Libwww (HTPostAnchor()) failed to start the "
+                                 "POST request.");
+        }
+        if (envP->fault_occurred)
+            rpcDestroy(rpcP);
+    }
+}
+
+
+
+struct xmlrpc_client_transport_ops xmlrpc_libwww_transport_ops = {
+    NULL,
+    NULL,
+    &create,
+    &destroy,
+    &sendRequest,
+    &call,
+    &finishAsynch,
+};