initial load of upstream version 1.06.32
[xmlrpc-c] / lib / curl_transport / xmlrpc_curl_transport.c
diff --git a/lib/curl_transport/xmlrpc_curl_transport.c b/lib/curl_transport/xmlrpc_curl_transport.c
new file mode 100644 (file)
index 0000000..530fab5
--- /dev/null
@@ -0,0 +1,1864 @@
+/*=============================================================================
+                           xmlrpc_curl_transport
+===============================================================================
+   Curl-based client transport for Xmlrpc-c
+
+   By Bryan Henderson 04.12.10.
+
+   Contributed to the public domain by its author.
+=============================================================================*/
+
+/*----------------------------------------------------------------------------
+   Curl global variables:
+
+   Curl maintains some minor information in process-global variables.
+   One must call curl_global_init() to initialize them before calling
+   any other Curl library function.  This is not state information --
+   it is constants.  They just aren't the kind of constants that the
+   library loader knows how to set, so there has to be this explicit
+   call to set them up.  The matching function curl_global_cleanup()
+   returns resources these use (to wit, the constants live in
+   malloc'ed storage and curl_global_cleanup() frees the storage).
+
+   So our setup_global_const transport operation calls
+   curl_global_init() and our teardown_global_const calls
+   curl_global_cleanup().
+
+   The Curl library is supposed to maintain a reference count for the
+   global constants so that multiple modules using the library and
+   independently calling curl_global_init() and curl_global_cleanup()
+   are not a problem.  But today, it just keeps a flag "I have been
+   initialized" and the first call to curl_global_cleanup() destroys
+   the constants for everybody.  Therefore, the user of the Xmlrpc-c
+   Curl client XML transport must make sure not to call
+   teardownGlobalConstants until everything else in his program is
+   done using the Curl library.
+
+   Note that curl_global_init() is not threadsafe (with or without the
+   reference count), therefore our setup_global_const is not, and must
+   be called when no other thread in the process is running.
+   Typically, one calls it right at the beginning of the program.
+
+   There are actually two other classes of global variables in the
+   Curl library, which we are ignoring: debug options and custom
+   memory allocator function identities.  Our code never changes these
+   global variables from default.  If something else in the user's
+   program does, User is responsible for making sure it doesn't
+   interfere with our use of the library.
+
+   Note that when we say what the Curl library does, we're also
+   talking about various other libraries Curl uses internally, and in
+   fact much of what we're saying about global variables springs from
+   such subordinate libraries as OpenSSL and Winsock.
+-----------------------------------------------------------------------------*/
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/time.h>
+
+#include "xmlrpc_config.h"
+
+#include "bool.h"
+#include "girmath.h"
+#include "mallocvar.h"
+#include "linklist.h"
+#include "girstring.h"
+#include "pthreadx.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"
+#include "xmlrpc-c/transport.h"
+#include "version.h"
+
+#include <curl/curl.h>
+#include <curl/types.h>
+#include <curl/easy.h>
+#include <curl/multi.h>
+
+#if defined (WIN32) && defined(_DEBUG)
+#  include <crtdbg.h>
+#  define new DEBUG_NEW
+#  define malloc(size) _malloc_dbg( size, _NORMAL_BLOCK, __FILE__, __LINE__)
+#  undef THIS_FILE
+   static char THIS_FILE[] = __FILE__;
+#endif /*WIN32 && _DEBUG*/
+
+
+
+struct curlSetup {
+
+    /* This is all client transport properties that are implemented as
+       simple Curl session properties (i.e. the transport basically just
+       passes them through to Curl without looking at them).
+
+       People occasionally want to replace all this with something where
+       the Xmlrpc-c user simply does the curl_easy_setopt() call and this
+       code need not know about all these options.  Unfortunately, that's
+       a significant modularity violation.  Either the Xmlrpc-c user
+       controls the Curl object or he doesn't.  If he does, then he
+       shouldn't use libxmlrpc_client -- he should just copy some of this
+       code into his own program.  If he doesn't, then he should never see
+       the Curl library.
+
+       Speaking of modularity: the only reason this is a separate struct
+       is to make the code easier to manage.  Ideally, the fact that these
+       particular properties of the transport are implemented by simple
+       Curl session setup would be known only at the lowest level code
+       that does that setup.
+    */
+
+    const char * networkInterface;
+        /* This identifies the network interface on the local side to
+           use for the session.  It is an ASCIIZ string in the form
+           that the Curl recognizes for setting its CURLOPT_INTERFACE
+           option (also the --interface option of the Curl program).
+           E.g. "9.1.72.189" or "giraffe-data.com" or "eth0".  
+
+           It isn't necessarily valid, but it does have a terminating NUL.
+
+           NULL means we have no preference.
+        */
+    xmlrpc_bool sslVerifyPeer;
+        /* In an SSL connection, we should authenticate the server's SSL
+           certificate -- refuse to talk to him if it isn't authentic.
+           This is equivalent to Curl's CURLOPT_SSL_VERIFY_PEER option.
+        */
+    xmlrpc_bool sslVerifyHost;
+        /* In an SSL connection, we should verify that the server's
+           certificate (independently of whether the certificate is
+           authentic) indicates the host name that is in the URL we
+           are using for the server.
+        */
+
+    const char * sslCert;
+    const char * sslCertType;
+    const char * sslCertPasswd;
+    const char * sslKey;
+    const char * sslKeyType;
+    const char * sslKeyPasswd;
+    const char * sslEngine;
+    bool         sslEngineDefault;
+    unsigned int sslVersion;
+    const char * caInfo;
+    const char * caPath;
+    const char * randomFile;
+    const char * egdSocket;
+    const char * sslCipherList;
+};
+
+
+/*============================================================================
+      locks
+==============================================================================
+   This is the beginnings of a lock abstraction that will allow this
+   transport to be used with locks other than pthread locks
+============================================================================*/
+
+struct lock {
+    pthread_mutex_t theLock;
+    void (*lock)(struct lock *);
+    void (*unlock)(struct lock *);
+    void (*destroy)(struct lock *);
+};
+
+typedef struct lock lock;
+
+static void
+lock_pthread(struct lock * const lockP) {
+    pthread_mutex_lock(&lockP->theLock);
+}
+
+static void
+unlock_pthread(struct lock * const lockP) {
+    pthread_mutex_unlock(&lockP->theLock);
+}
+
+static void
+destroyLock_pthread(struct lock * const lockP) {
+    pthread_mutex_destroy(&lockP->theLock);
+    free(lockP);
+}
+
+
+static struct lock *
+createLock_pthread(void) {
+    struct lock * lockP;
+    MALLOCVAR(lockP);
+    if (lockP) {
+        pthread_mutex_init(&lockP->theLock, NULL);
+        lockP->lock    = &lock_pthread;
+        lockP->unlock  = &unlock_pthread;
+        lockP->destroy = &destroyLock_pthread;
+    }
+    return lockP;
+}
+
+
+
+
+/*=============================================================================
+    curlMulti
+=============================================================================*/
+
+struct curlMulti {
+/*----------------------------------------------------------------------------
+   This is an extension to Curl's CURLM object.  The extensions are:
+
+   1) It has a lock so multiple threads can use it simultaneously.
+
+   2) Its "select" file descriptor vectors are self-contained.  CURLM
+      requires the user to maintain them separately.
+-----------------------------------------------------------------------------*/
+    CURLM * curlMultiP;
+    lock * lockP;
+        /* Hold this lock while accessing or using *curlMultiP.  You're
+           using the multi manager whenever you're calling a Curl
+           library multi manager function.
+        */
+    /* The following file descriptor sets are an integral part of the
+       CURLM object; Our curlMulti_fdset() routine binds them to the
+       CURLM object, and said object expects us to use them in a very
+       specific way, including doing a select() on them.  It is very,
+       very messy.
+    */
+    fd_set readFdSet;
+    fd_set writeFdSet;
+    fd_set exceptFdSet;
+};
+
+
+
+static struct curlMulti *
+createCurlMulti(void) {
+
+    struct curlMulti * retval;
+    struct curlMulti * curlMultiP;
+
+    MALLOCVAR(curlMultiP);
+
+    if (curlMultiP == NULL)
+        retval = NULL;
+    else {
+        curlMultiP->lockP = createLock_pthread();
+
+        if (curlMultiP->lockP == NULL)
+            retval = NULL;
+        else {
+            curlMultiP->curlMultiP = curl_multi_init();
+            if (curlMultiP->curlMultiP == NULL)
+                retval = NULL;
+            else
+                retval = curlMultiP;
+
+            if (retval == NULL)
+                curlMultiP->lockP->destroy(curlMultiP->lockP);
+        }
+        if (retval == NULL)
+            free(curlMultiP);
+    }
+    return retval;
+}
+
+
+
+static void
+destroyCurlMulti(struct curlMulti * const curlMultiP) {
+
+    curl_multi_cleanup(curlMultiP->curlMultiP);
+    
+    curlMultiP->lockP->destroy(curlMultiP->lockP);
+
+    free(curlMultiP);
+}
+
+
+
+static void
+curlMulti_perform(xmlrpc_env *       const envP,
+                  struct curlMulti * const curlMultiP,
+                  bool *             const immediateWorkToDoP,
+                  int *              const runningHandlesP) {
+
+    CURLMcode rc;
+
+    curlMultiP->lockP->lock(curlMultiP->lockP);
+
+    rc = curl_multi_perform(curlMultiP->curlMultiP, runningHandlesP);
+
+    curlMultiP->lockP->unlock(curlMultiP->lockP);
+
+    if (rc == CURLM_CALL_MULTI_PERFORM) {
+        *immediateWorkToDoP = true;
+    } else {
+        *immediateWorkToDoP = false;
+
+        if (rc != CURLM_OK) {
+            xmlrpc_faultf(envP,
+                          "Impossible failure of curl_multi_perform() "
+                          "with rc %d", rc);
+        }
+    }
+}        
+
+
+
+static void
+curlMulti_addHandle(xmlrpc_env *       const envP,
+                    struct curlMulti * const curlMultiP,
+                    CURL *             const curlSessionP) {
+
+    CURLMcode rc;
+
+    curlMultiP->lockP->lock(curlMultiP->lockP);
+
+    rc = curl_multi_add_handle(curlMultiP->curlMultiP, curlSessionP);
+    
+    curlMultiP->lockP->unlock(curlMultiP->lockP);
+
+    if (rc != CURLM_OK)
+        xmlrpc_faultf(envP, "Could not add Curl session to the "
+                      "curl multi manager.  curl_multi_add_handle() "
+                      "returns error code %d", rc);
+}
+
+
+static void
+curlMulti_removeHandle(struct curlMulti * const curlMultiP,
+                       CURL *             const curlSessionP) {
+
+    curlMultiP->lockP->lock(curlMultiP->lockP);
+
+    curl_multi_remove_handle(curlMultiP->curlMultiP, curlSessionP);
+    
+    curlMultiP->lockP->unlock(curlMultiP->lockP);
+}
+
+
+
+static void
+curlMulti_getMessage(struct curlMulti * const curlMultiP,
+                     bool *             const endOfMessagesP,
+                     CURLMsg *          const curlMsgP) {
+/*----------------------------------------------------------------------------
+   Get the next message from the queue of things the Curl multi manager
+   wants to say to us.
+
+   Return the message as *curlMsgP.
+
+   Iff there are no messages in the queue, return *endOfMessagesP == true.
+-----------------------------------------------------------------------------*/
+    int remainingMsgCount;
+    CURLMsg * privateCurlMsgP;
+        /* Note that this is a pointer into the multi manager's memory,
+           so we have to use it under lock.
+        */
+
+    curlMultiP->lockP->lock(curlMultiP->lockP);
+    
+    privateCurlMsgP = curl_multi_info_read(curlMultiP->curlMultiP,
+                                           &remainingMsgCount);
+        
+    if (privateCurlMsgP == NULL)
+        *endOfMessagesP = true;
+    else {
+        *endOfMessagesP = false;
+        *curlMsgP = *privateCurlMsgP;
+    }    
+    curlMultiP->lockP->unlock(curlMultiP->lockP);
+}
+
+
+
+static void
+curlMulti_fdset(xmlrpc_env *       const envP,
+                struct curlMulti * const curlMultiP,
+                fd_set *           const readFdSetP,
+                fd_set *           const writeFdSetP,
+                fd_set *           const exceptFdSetP,
+                int *              const maxFdP) {
+/*----------------------------------------------------------------------------
+   Set the CURLM object's file descriptor sets to those in the
+   curlMulti object, update those file descriptor sets with the
+   current needs of the multi manager, and return the resulting values
+   of the file descriptor sets.
+
+   This is a bizarre operation, but is necessary because of the nonmodular
+   way in which the Curl multi interface works with respect to waiting
+   for work with select().
+-----------------------------------------------------------------------------*/
+    CURLMcode rc;
+    
+    curlMultiP->lockP->lock(curlMultiP->lockP);
+
+    /* curl_multi_fdset() doesn't _set_ the fdsets.  It adds to existing
+       ones (so you can easily do a select() on other fds and Curl
+       fds at the same time).  So we have to clear first:
+    */
+    FD_ZERO(&curlMultiP->readFdSet);
+    FD_ZERO(&curlMultiP->writeFdSet);
+    FD_ZERO(&curlMultiP->exceptFdSet);
+
+    /* WARNING: curl_multi_fdset() doesn't just update the fdsets pointed
+       to by its arguments.  It makes the CURLM object remember those
+       pointers and refer back to them later!  In fact, curl_multi_perform
+       expects its caller to have done a select() on those masks.  No,
+       really.  The man page even admits it.
+    */
+
+    rc = curl_multi_fdset(curlMultiP->curlMultiP,
+                          &curlMultiP->readFdSet,
+                          &curlMultiP->writeFdSet,
+                          &curlMultiP->exceptFdSet,
+                          maxFdP);
+
+    *readFdSetP   = curlMultiP->readFdSet;
+    *writeFdSetP  = curlMultiP->writeFdSet;
+    *exceptFdSetP = curlMultiP->exceptFdSet;
+
+    curlMultiP->lockP->unlock(curlMultiP->lockP);
+
+    if (rc != CURLM_OK)
+        xmlrpc_faultf(envP, "Impossible failure of curl_multi_fdset() "
+                      "with rc %d", rc);
+}
+
+
+
+static void
+curlMulti_updateFdSet(struct curlMulti * const curlMultiP,
+                      fd_set             const readFdSet,
+                      fd_set             const writeFdSet,
+                      fd_set             const exceptFdSet) {
+/*----------------------------------------------------------------------------
+   curl_multi_perform() expects the file descriptor sets, which were bound
+   to the CURLM object via a prior curlMulti_fdset(), to contain the results
+   of a recent select().  This subroutine provides you a way to supply those.
+-----------------------------------------------------------------------------*/
+    curlMultiP->readFdSet   = readFdSet;
+    curlMultiP->writeFdSet  = writeFdSet;
+    curlMultiP->exceptFdSet = exceptFdSet;
+}
+
+                      
+
+/*===========================================================================*/
+
+
+struct xmlrpc_client_transport {
+    struct curlMulti * curlMultiP;
+        /* The Curl multi manager that this transport uses to handle
+           multiple Curl sessions at the same time.
+        */
+    CURL * syncCurlSessionP;
+        /* Handle for a Curl library session object that we use for
+           all synchronous RPCs.  An async RPC has one of its own,
+           and consequently does not share things such as persistent
+           connections and cookies with any other RPC.
+        */
+    lock * syncCurlSessionLockP;
+        /* Hold this lock while accessing or using *syncCurlSessionP.
+           You're using the session from the time you set any
+           attributes in it or start a transaction with it until any
+           transaction has finished and you've lost interest in any
+           attributes of the session.
+        */
+    const char * userAgent;
+        /* Prefix for the User-Agent HTTP header, reflecting facilities
+           outside of Xmlrpc-c.  The actual User-Agent header consists
+           of this prefix plus information about Xmlrpc-c.  NULL means
+           none.
+
+           This is constant.
+        */
+    struct curlSetup curlSetupStuff;
+        /* This is constant */
+};
+
+typedef struct {
+    /* This is all stuff that really ought to be in a Curl object, but
+       the Curl library is a little too simple for that.  So we build
+       a layer on top of Curl, and define this "transaction," as an
+       object subordinate to a Curl "session."  A Curl session has
+       zero or one transactions in progress.  The Curl session
+       "private data" is a pointer to the CurlTransaction object for
+       the current transaction.
+    */
+    CURL * curlSessionP;
+        /* Handle for the Curl session that hosts this transaction.
+           Note that only one transaction at a time can use a particular
+           Curl session, so this had better not be a session that some other
+           transaction is using simultaneously.
+        */
+    struct curlMulti * curlMultiP;
+        /* The Curl multi manager which manages the above curl session,
+           if any.  An asynchronous process uses a Curl multi manager
+           to manage the in-progress Curl sessions and thereby in-progress
+           RPCs.  A synchronous process has no need of a Curl multi manager.
+        */
+    struct rpc * rpcP;
+        /* The RPC which this transaction serves.  (If this structure
+           were a true extension of the Curl library as described above,
+           this would be a void *, since the Curl library doesn't know what
+           an RPC is, but since we use it only for that, we might as well
+           use the specific type here).
+        */
+    char curlError[CURL_ERROR_SIZE];
+        /* Error message from Curl */
+    struct curl_slist * headerList;
+        /* The HTTP headers for the transaction */
+    const char * serverUrl;  /* malloc'ed - belongs to this object */
+} curlTransaction;
+
+
+
+typedef struct rpc {
+    curlTransaction * curlTransactionP;
+        /* The object which does the HTTP transaction, with no knowledge
+           of XML-RPC or Xmlrpc-c.
+        */
+    xmlrpc_mem_block * responseXmlP;
+        /* Where the response XML for this RPC should go or has gone. */
+    xmlrpc_transport_asynch_complete complete;
+        /* Routine to call to complete the RPC after it is complete HTTP-wise.
+           NULL if none.
+        */
+    struct xmlrpc_call_info * callInfoP;
+        /* User's identifier for this RPC */
+} rpc;
+
+
+static int
+timeDiffMillisec(struct timeval const minuend,
+                 struct timeval const subtractor) {
+
+    return (minuend.tv_sec - subtractor.tv_sec) * 1000 +
+        (minuend.tv_usec - subtractor.tv_usec + 500) / 1000;
+}
+
+
+
+static bool
+timeIsAfter(struct timeval const comparator,
+            struct timeval const comparand) {
+
+    if (comparator.tv_sec > comparand.tv_sec)
+        return true;
+    else if (comparator.tv_sec < comparand.tv_sec)
+        return false;
+    else {
+        /* Seconds are equal */
+        if (comparator.tv_usec > comparand.tv_usec)
+            return true;
+        else
+            return false;
+    }
+}
+
+
+
+static void
+addMilliseconds(struct timeval   const addend,
+                unsigned int     const adder,
+                struct timeval * const sumP) {
+
+    unsigned int newRawUsec;
+
+    newRawUsec = addend.tv_usec + adder * 1000;
+
+    sumP->tv_sec  = addend.tv_sec + newRawUsec / 1000000;
+    sumP->tv_usec = newRawUsec % 1000000;
+}
+
+
+
+static void
+lockSyncCurlSession(struct xmlrpc_client_transport * const transportP) {
+    transportP->syncCurlSessionLockP->lock(transportP->syncCurlSessionLockP);
+}
+
+
+
+static void
+unlockSyncCurlSession(struct xmlrpc_client_transport * const transportP) {
+    transportP->syncCurlSessionLockP->unlock(transportP->syncCurlSessionLockP);
+}
+
+
+
+static size_t 
+collect(void *  const ptr, 
+        size_t  const size, 
+        size_t  const nmemb,  
+        FILE  * const stream) {
+/*----------------------------------------------------------------------------
+   This is a Curl output function.  Curl calls this to deliver the
+   HTTP response body.  Curl thinks it's writing to a POSIX stream.
+-----------------------------------------------------------------------------*/
+    xmlrpc_mem_block * const responseXmlP = (xmlrpc_mem_block *) stream;
+    char * const buffer = ptr;
+    size_t const length = nmemb * size;
+
+    size_t retval;
+    xmlrpc_env env;
+
+    xmlrpc_env_init(&env);
+    xmlrpc_mem_block_append(&env, responseXmlP, buffer, length);
+    if (env.fault_occurred)
+        retval = (size_t)-1;
+    else
+        /* Really?  Shouldn't it be like fread() and return 'nmemb'? */
+        retval = length;
+    
+    return retval;
+}
+
+
+
+static void
+initWindowsStuff(xmlrpc_env * const envP ATTR_UNUSED) {
+
+#if defined (WIN32)
+    /* This is CRITICAL so that cURL-Win32 works properly! */
+    
+    /* So this commenter says, but I wonder why.  libcurl should do the
+       required WSAStartup() itself, and it looks to me like it does.
+       -Bryan 06.01.01
+    */
+    WORD wVersionRequested;
+    WSADATA wsaData;
+    int err;
+    wVersionRequested = MAKEWORD(1, 1);
+    
+    err = WSAStartup(wVersionRequested, &wsaData);
+    if (err)
+        xmlrpc_env_set_fault_formatted(
+            envP, XMLRPC_INTERNAL_ERROR,
+            "Winsock startup failed.  WSAStartup returned rc %d", err);
+    else {
+        if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
+            /* Tell the user that we couldn't find a useable */ 
+            /* winsock.dll. */ 
+            xmlrpc_env_set_fault_formatted(
+                envP, XMLRPC_INTERNAL_ERROR, "Winsock reported that "
+                "it does not implement the requested version 1.1.");
+        }
+        if (envP->fault_occurred)
+            WSACleanup();
+    }
+#endif
+}
+
+
+
+static void
+termWindowsStuff(void) {
+
+#if defined (WIN32)
+    WSACleanup();
+#endif
+}
+
+
+
+static void
+getXportParms(xmlrpc_env *  const envP ATTR_UNUSED,
+              const struct xmlrpc_curl_xportparms * const curlXportParmsP,
+              size_t        const parmSize,
+              struct xmlrpc_client_transport * const transportP) {
+/*----------------------------------------------------------------------------
+   Get the parameters out of *curlXportParmsP and update *transportP
+   to reflect them.
+
+   *curlXportParmsP is a 'parmSize' bytes long prefix of
+   struct xmlrpc_curl_xportparms.
+
+   curlXportParmsP is something the user created.  It's designed to be
+   friendly to the user, not to this program, and is encumbered by
+   lots of backward compatibility constraints.  In particular, the
+   user may have coded and/or compiled it at a time that struct
+   xmlrpc_curl_xportparms was smaller than it is now!
+
+   So that's why we don't simply attach a copy of *curlXportParmsP to
+   *transportP.
+
+   To the extent that *curlXportParmsP is too small to contain a parameter,
+   we return the default value for that parameter.
+
+   Special case:  curlXportParmsP == NULL means there is no input at all.
+   In that case, we return default values for everything.
+-----------------------------------------------------------------------------*/
+    struct curlSetup * const curlSetupP = &transportP->curlSetupStuff;
+
+    if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(user_agent))
+        transportP->userAgent = NULL;
+    else if (curlXportParmsP->user_agent == NULL)
+        transportP->userAgent = NULL;
+    else
+        transportP->userAgent = strdup(curlXportParmsP->user_agent);
+    
+    if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(network_interface))
+        curlSetupP->networkInterface = NULL;
+    else if (curlXportParmsP->network_interface == NULL)
+        curlSetupP->networkInterface = NULL;
+    else
+        curlSetupP->networkInterface =
+            strdup(curlXportParmsP->network_interface);
+
+    if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(no_ssl_verifypeer))
+        curlSetupP->sslVerifyPeer = true;
+    else
+        curlSetupP->sslVerifyPeer = !curlXportParmsP->no_ssl_verifypeer;
+        
+    if (!curlXportParmsP || 
+        parmSize < XMLRPC_CXPSIZE(no_ssl_verifyhost))
+        curlSetupP->sslVerifyHost = true;
+    else
+        curlSetupP->sslVerifyHost = !curlXportParmsP->no_ssl_verifyhost;
+
+    if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(ssl_cert))
+        curlSetupP->sslCert = NULL;
+    else if (curlXportParmsP->ssl_cert == NULL)
+        curlSetupP->sslCert = NULL;
+    else
+        curlSetupP->sslCert = strdup(curlXportParmsP->ssl_cert);
+    
+    if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslcerttype))
+        curlSetupP->sslCertType = NULL;
+    else if (curlXportParmsP->sslcerttype == NULL)
+        curlSetupP->sslCertType = NULL;
+    else
+        curlSetupP->sslCertType = strdup(curlXportParmsP->sslcerttype);
+    
+    if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslcertpasswd))
+        curlSetupP->sslCertPasswd = NULL;
+    else if (curlXportParmsP->sslcertpasswd == NULL)
+        curlSetupP->sslCertPasswd = NULL;
+    else
+        curlSetupP->sslCertPasswd = strdup(curlXportParmsP->sslcertpasswd);
+    
+    if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslkey))
+        curlSetupP->sslKey = NULL;
+    else if (curlXportParmsP->sslkey == NULL)
+        curlSetupP->sslKey = NULL;
+    else
+        curlSetupP->sslKey = strdup(curlXportParmsP->sslkey);
+    
+    if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslkeytype))
+        curlSetupP->sslKeyType = NULL;
+    else if (curlXportParmsP->sslkeytype == NULL)
+        curlSetupP->sslKeyType = NULL;
+    else
+        curlSetupP->sslKeyType = strdup(curlXportParmsP->sslkeytype);
+    
+        if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslkeypasswd))
+        curlSetupP->sslKeyPasswd = NULL;
+    else if (curlXportParmsP->sslkeypasswd == NULL)
+        curlSetupP->sslKeyPasswd = NULL;
+    else
+        curlSetupP->sslKeyPasswd = strdup(curlXportParmsP->sslkeypasswd);
+    
+    if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslengine))
+        curlSetupP->sslEngine = NULL;
+    else if (curlXportParmsP->sslengine == NULL)
+        curlSetupP->sslEngine = NULL;
+    else
+        curlSetupP->sslEngine = strdup(curlXportParmsP->sslengine);
+    
+    if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslengine_default))
+        curlSetupP->sslEngineDefault = false;
+    else
+        curlSetupP->sslEngineDefault = !!curlXportParmsP->sslengine_default;
+    
+    if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslversion))
+        curlSetupP->sslVersion = XMLRPC_SSLVERSION_DEFAULT;
+    else
+        curlSetupP->sslVersion = curlXportParmsP->sslversion;
+    
+    if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(cainfo))
+        curlSetupP->caInfo = NULL;
+    else if (curlXportParmsP->cainfo == NULL)
+        curlSetupP->caInfo = NULL;
+    else
+        curlSetupP->caInfo = strdup(curlXportParmsP->cainfo);
+    
+    if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(capath))
+        curlSetupP->caPath = NULL;
+    else if (curlXportParmsP->capath == NULL)
+        curlSetupP->caPath = NULL;
+    else
+        curlSetupP->caPath = strdup(curlXportParmsP->capath);
+    
+    if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(randomfile))
+        curlSetupP->randomFile = NULL;
+    else if (curlXportParmsP->randomfile == NULL)
+        curlSetupP->randomFile = NULL;
+    else
+        curlSetupP->randomFile = strdup(curlXportParmsP->randomfile);
+    
+    if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(egdsocket))
+        curlSetupP->egdSocket = NULL;
+    else if (curlXportParmsP->egdsocket == NULL)
+        curlSetupP->egdSocket = NULL;
+    else
+        curlSetupP->egdSocket = strdup(curlXportParmsP->egdsocket);
+    
+    if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(ssl_cipher_list))
+        curlSetupP->sslCipherList = NULL;
+    else if (curlXportParmsP->ssl_cipher_list == NULL)
+        curlSetupP->sslCipherList = NULL;
+    else
+        curlSetupP->sslCipherList = strdup(curlXportParmsP->ssl_cipher_list);
+
+}
+
+
+
+static void
+freeXportParms(const struct xmlrpc_client_transport * const transportP) {
+
+    const struct curlSetup * const curlSetupP = &transportP->curlSetupStuff;
+
+    if (curlSetupP->sslCipherList)
+        xmlrpc_strfree(curlSetupP->sslCipherList);
+    if (curlSetupP->egdSocket)
+        xmlrpc_strfree(curlSetupP->egdSocket);
+    if (curlSetupP->randomFile)
+        xmlrpc_strfree(curlSetupP->randomFile);
+    if (curlSetupP->caPath)
+        xmlrpc_strfree(curlSetupP->caPath);
+    if (curlSetupP->caInfo)
+        xmlrpc_strfree(curlSetupP->caInfo);
+    if (curlSetupP->sslEngine)
+        xmlrpc_strfree(curlSetupP->sslEngine);
+    if (curlSetupP->sslKeyPasswd)
+        xmlrpc_strfree(curlSetupP->sslKeyPasswd);
+    if (curlSetupP->sslKeyType)
+        xmlrpc_strfree(curlSetupP->sslKeyType);
+    if (curlSetupP->sslKey)
+        xmlrpc_strfree(curlSetupP->sslKey);
+    if (curlSetupP->sslCertPasswd)
+        xmlrpc_strfree(curlSetupP->sslCertPasswd);
+    if (curlSetupP->sslCertType)
+        xmlrpc_strfree(curlSetupP->sslCertType);
+    if (curlSetupP->sslCert)
+        xmlrpc_strfree(curlSetupP->sslCert);
+    if (curlSetupP->networkInterface)
+        xmlrpc_strfree(curlSetupP->networkInterface);
+    if (transportP->userAgent)
+        xmlrpc_strfree(transportP->userAgent);
+}
+
+
+
+static void
+createSyncCurlSession(xmlrpc_env * const envP,
+                      CURL **      const curlSessionPP) {
+/*----------------------------------------------------------------------------
+   Create a Curl session to be used for multiple serial transactions.
+   The Curl session we create is not complete -- it still has to be
+   further set up for each particular transaction.
+
+   We can't set up anything here that changes from one transaction to the
+   next.
+
+   We don't bother setting up anything that has to be set up for an
+   asynchronous transaction because code that is common between synchronous
+   and asynchronous transactions takes care of that anyway.
+
+   That leaves things, such as cookies, that don't exist for
+   asynchronous transactions, and are common to multiple serial
+   synchronous transactions.
+-----------------------------------------------------------------------------*/
+    CURL * const curlSessionP = curl_easy_init();
+
+    if (curlSessionP == NULL)
+        xmlrpc_faultf(envP, "Could not create Curl session.  "
+                      "curl_easy_init() failed.");
+    else {
+        /* The following is a trick.  CURLOPT_COOKIEFILE is the name
+           of the file containing the initial cookies for the Curl
+           session.  But setting it is also what turns on the cookie
+           function itself, whereby the Curl library accepts and
+           stores cookies from the server and sends them back on
+           future requests.  We don't have a file of initial cookies, but
+           we want to turn on cookie function, so we set the option to
+           something we know does not validly name a file.  Curl will
+           ignore the error and just start up cookie function with no
+           initial cookies.
+        */
+        curl_easy_setopt(curlSessionP, CURLOPT_COOKIEFILE, "");
+
+        *curlSessionPP = curlSessionP;
+    }
+}
+
+
+
+static void
+destroySyncCurlSession(CURL * const curlSessionP) {
+
+    curl_easy_cleanup(curlSessionP);
+}
+
+
+
+static void
+makeSyncCurlSession(xmlrpc_env *                     const envP,
+                    struct xmlrpc_client_transport * const transportP) {
+
+    transportP->syncCurlSessionLockP = createLock_pthread();
+    if (transportP->syncCurlSessionLockP == NULL)
+        xmlrpc_faultf(envP, "Unable to create lock for "
+                      "synchronous Curl session.");
+    else {
+        createSyncCurlSession(envP, &transportP->syncCurlSessionP);
+        if (envP->fault_occurred)
+            transportP->syncCurlSessionLockP->destroy(
+                transportP->syncCurlSessionLockP); 
+    }
+}
+
+
+
+static void
+unmakeSyncCurlSession(struct xmlrpc_client_transport * const transportP) {
+
+    destroySyncCurlSession(transportP->syncCurlSessionP);
+
+    transportP->syncCurlSessionLockP->destroy(
+        transportP->syncCurlSessionLockP); 
+}
+
+
+
+static void
+assertConstantsMatch(void) {
+/*----------------------------------------------------------------------------
+   There are some constants that we define as part of the Xmlrpc-c
+   interface that are identical to constants in the Curl interface to
+   make curl option setting work.  This function asserts such
+   formally.
+-----------------------------------------------------------------------------*/
+    assert(XMLRPC_SSLVERSION_DEFAULT == CURL_SSLVERSION_DEFAULT);
+    assert(XMLRPC_SSLVERSION_TLSv1   == CURL_SSLVERSION_TLSv1);
+    assert(XMLRPC_SSLVERSION_SSLv2   == CURL_SSLVERSION_SSLv2);
+    assert(XMLRPC_SSLVERSION_SSLv3   == CURL_SSLVERSION_SSLv3);
+}
+
+
+
+static void 
+create(xmlrpc_env *                      const envP,
+       int                               const flags ATTR_UNUSED,
+       const char *                      const appname ATTR_UNUSED,
+       const char *                      const appversion ATTR_UNUSED,
+       const struct xmlrpc_xportparms *  const transportparmsP,
+       size_t                            const parm_size,
+       struct xmlrpc_client_transport ** const handlePP) {
+/*----------------------------------------------------------------------------
+   This does the 'create' operation for a Curl client transport.
+-----------------------------------------------------------------------------*/
+    struct xmlrpc_curl_xportparms * const curlXportParmsP = 
+        (struct xmlrpc_curl_xportparms *) transportparmsP;
+
+    struct xmlrpc_client_transport * transportP;
+
+    assertConstantsMatch();
+
+    MALLOCVAR(transportP);
+    if (transportP == NULL)
+        xmlrpc_faultf(envP, "Unable to allocate transport descriptor.");
+    else {
+        transportP->curlMultiP = createCurlMulti();
+        
+        if (transportP->curlMultiP == NULL)
+            xmlrpc_faultf(envP, "Unable to create Curl multi manager");
+        else {
+            getXportParms(envP, curlXportParmsP, parm_size, transportP);
+
+            if (!envP->fault_occurred) {
+                makeSyncCurlSession(envP, transportP);
+
+                if (envP->fault_occurred)
+                    freeXportParms(transportP);
+            }
+            if (envP->fault_occurred)
+                destroyCurlMulti(transportP->curlMultiP);
+        }                 
+        if (envP->fault_occurred)
+            free(transportP);
+    }
+    *handlePP = transportP;
+}
+
+
+
+static void
+assertNoOutstandingCurlWork(struct curlMulti * const curlMultiP) {
+
+    xmlrpc_env env;
+    bool immediateWorkToDo;
+    int runningHandles;
+    
+    xmlrpc_env_init(&env);
+    
+    curlMulti_perform(&env, curlMultiP, &immediateWorkToDo, &runningHandles);
+    
+    /* We know the above was a no-op, since we're asserting that there
+       is no outstanding work.
+    */
+    XMLRPC_ASSERT(!env.fault_occurred);
+    XMLRPC_ASSERT(!immediateWorkToDo);
+    XMLRPC_ASSERT(runningHandles == 0);
+    xmlrpc_env_clean(&env);
+}
+
+
+
+static void 
+destroy(struct xmlrpc_client_transport * const clientTransportP) {
+/*----------------------------------------------------------------------------
+   This does the 'destroy' operation for a Curl client transport.
+-----------------------------------------------------------------------------*/
+    XMLRPC_ASSERT(clientTransportP != NULL);
+
+    assertNoOutstandingCurlWork(clientTransportP->curlMultiP);
+        /* We know this is true because a condition of destroying the
+           transport is that there be no outstanding RPCs.
+        */
+    unmakeSyncCurlSession(clientTransportP);
+
+    destroyCurlMulti(clientTransportP->curlMultiP);
+
+    freeXportParms(clientTransportP);
+
+    free(clientTransportP);
+}
+
+
+
+static void
+addHeader(xmlrpc_env * const envP,
+          struct curl_slist ** const headerListP,
+          const char *         const headerText) {
+
+    struct curl_slist * newHeaderList;
+    newHeaderList = curl_slist_append(*headerListP, headerText);
+    if (newHeaderList == NULL)
+        xmlrpc_faultf(envP,
+                      "Could not add header '%s'.  "
+                      "curl_slist_append() failed.", headerText);
+    else
+        *headerListP = newHeaderList;
+}
+
+
+
+static void
+addContentTypeHeader(xmlrpc_env *         const envP,
+                     struct curl_slist ** const headerListP) {
+    
+    addHeader(envP, headerListP, "Content-Type: text/xml");
+}
+
+
+
+static void
+addUserAgentHeader(xmlrpc_env *         const envP,
+                   struct curl_slist ** const headerListP,
+                   const char *         const userAgent) {
+    
+    if (userAgent) {
+        curl_version_info_data * const curlInfoP =
+            curl_version_info(CURLVERSION_NOW);
+        char curlVersion[32];
+        const char * userAgentHeader;
+        
+        snprintf(curlVersion, sizeof(curlVersion), "%u.%u.%u",
+                (curlInfoP->version_num >> 16) && 0xff,
+                (curlInfoP->version_num >>  8) && 0xff,
+                (curlInfoP->version_num >>  0) && 0xff
+            );
+                  
+        xmlrpc_asprintf(&userAgentHeader,
+                        "User-Agent: %s Xmlrpc-c/%s Curl/%s",
+                        userAgent, XMLRPC_C_VERSION, curlVersion);
+        
+        if (userAgentHeader == xmlrpc_strsol)
+            xmlrpc_faultf(envP, "Couldn't allocate memory for "
+                          "User-Agent header");
+        else {
+            addHeader(envP, headerListP, userAgentHeader);
+            
+            xmlrpc_strfree(userAgentHeader);
+        }
+    }
+}
+
+
+
+static void
+addAuthorizationHeader(xmlrpc_env *         const envP,
+                       struct curl_slist ** const headerListP,
+                       const char *         const basicAuthInfo) {
+
+    if (basicAuthInfo) {
+        const char * authorizationHeader;
+            
+        xmlrpc_asprintf(&authorizationHeader, "Authorization: %s",
+                        basicAuthInfo);
+            
+        if (authorizationHeader == xmlrpc_strsol)
+            xmlrpc_faultf(envP, "Couldn't allocate memory for "
+                          "Authorization header");
+        else {
+            addHeader(envP, headerListP, authorizationHeader);
+
+            xmlrpc_strfree(authorizationHeader);
+        }
+    }
+}
+
+
+
+static void
+createCurlHeaderList(xmlrpc_env *               const envP,
+                     const xmlrpc_server_info * const serverP,
+                     const char *               const userAgent,
+                     struct curl_slist **       const headerListP) {
+
+    struct curl_slist * headerList;
+
+    headerList = NULL;  /* initial value - empty list */
+
+    addContentTypeHeader(envP, &headerList);
+    if (!envP->fault_occurred) {
+        addUserAgentHeader(envP, &headerList, userAgent);
+        if (!envP->fault_occurred) {
+            addAuthorizationHeader(envP, &headerList, 
+                                   serverP->_http_basic_auth);
+        }
+    }
+    if (envP->fault_occurred)
+        curl_slist_free_all(headerList);
+    else
+        *headerListP = headerList;
+}
+
+
+
+static void
+setupCurlSession(xmlrpc_env *             const envP,
+                 curlTransaction *        const curlTransactionP,
+                 xmlrpc_mem_block *       const callXmlP,
+                 xmlrpc_mem_block *       const responseXmlP,
+                 const struct curlSetup * const curlSetupP) {
+/*----------------------------------------------------------------------------
+   Set up the Curl session for the transaction *curlTransactionP so that
+   a subsequent curl_easy_perform() will perform said transaction.
+-----------------------------------------------------------------------------*/
+    CURL * const curlSessionP = curlTransactionP->curlSessionP;
+
+    assertConstantsMatch();
+
+    curl_easy_setopt(curlSessionP, CURLOPT_POST, 1);
+    curl_easy_setopt(curlSessionP, CURLOPT_URL, curlTransactionP->serverUrl);
+
+    XMLRPC_MEMBLOCK_APPEND(char, envP, callXmlP, "\0", 1);
+    if (!envP->fault_occurred) {
+        curl_easy_setopt(curlSessionP, CURLOPT_POSTFIELDS, 
+                         XMLRPC_MEMBLOCK_CONTENTS(char, callXmlP));
+        
+        curl_easy_setopt(curlSessionP, CURLOPT_WRITEFUNCTION, collect);
+        curl_easy_setopt(curlSessionP, CURLOPT_FILE, responseXmlP);
+        curl_easy_setopt(curlSessionP, CURLOPT_HEADER, 0);
+        curl_easy_setopt(curlSessionP, CURLOPT_ERRORBUFFER, 
+                         curlTransactionP->curlError);
+        curl_easy_setopt(curlSessionP, CURLOPT_NOPROGRESS, 1);
+        
+        curl_easy_setopt(curlSessionP, CURLOPT_HTTPHEADER, 
+                         curlTransactionP->headerList);
+
+        curl_easy_setopt(curlSessionP, CURLOPT_SSL_VERIFYPEER,
+                         curlSetupP->sslVerifyPeer);
+        curl_easy_setopt(curlSessionP, CURLOPT_SSL_VERIFYHOST,
+                         curlSetupP->sslVerifyHost ? 2 : 0);
+
+        if (curlSetupP->networkInterface)
+            curl_easy_setopt(curlSessionP, CURLOPT_INTERFACE,
+                             curlSetupP->networkInterface);
+        if (curlSetupP->sslCert)
+            curl_easy_setopt(curlSessionP, CURLOPT_SSLCERT,
+                             curlSetupP->sslCert);
+        if (curlSetupP->sslCertType)
+            curl_easy_setopt(curlSessionP, CURLOPT_SSLCERTTYPE,
+                             curlSetupP->sslCertType);
+        if (curlSetupP->sslCertPasswd)
+            curl_easy_setopt(curlSessionP, CURLOPT_SSLCERTPASSWD,
+                             curlSetupP->sslCertPasswd);
+        if (curlSetupP->sslKey)
+            curl_easy_setopt(curlSessionP, CURLOPT_SSLKEY,
+                             curlSetupP->sslKey);
+        if (curlSetupP->sslKeyType)
+            curl_easy_setopt(curlSessionP, CURLOPT_SSLKEYTYPE,
+                             curlSetupP->sslKeyType);
+        if (curlSetupP->sslKeyPasswd)
+            curl_easy_setopt(curlSessionP, CURLOPT_SSLKEYPASSWD,
+                             curlSetupP->sslKeyPasswd);
+        if (curlSetupP->sslEngine)
+            curl_easy_setopt(curlSessionP, CURLOPT_SSLENGINE,
+                             curlSetupP->sslEngine);
+        if (curlSetupP->sslEngineDefault)
+            /* 3rd argument seems to be required by some Curl */
+            curl_easy_setopt(curlSessionP, CURLOPT_SSLENGINE_DEFAULT, 1l);
+        if (curlSetupP->sslVersion != XMLRPC_SSLVERSION_DEFAULT)
+            curl_easy_setopt(curlSessionP, CURLOPT_SSLVERSION,
+                             curlSetupP->sslVersion);
+        if (curlSetupP->caInfo)
+            curl_easy_setopt(curlSessionP, CURLOPT_CAINFO,
+                             curlSetupP->caInfo);
+        if (curlSetupP->caPath)
+            curl_easy_setopt(curlSessionP, CURLOPT_CAPATH,
+                             curlSetupP->caPath);
+        if (curlSetupP->randomFile)
+            curl_easy_setopt(curlSessionP, CURLOPT_RANDOM_FILE,
+                             curlSetupP->randomFile);
+        if (curlSetupP->egdSocket)
+            curl_easy_setopt(curlSessionP, CURLOPT_EGDSOCKET,
+                             curlSetupP->egdSocket);
+        if (curlSetupP->sslCipherList)
+            curl_easy_setopt(curlSessionP, CURLOPT_SSL_CIPHER_LIST,
+                             curlSetupP->sslCipherList);
+    }
+}
+
+
+
+static void
+createCurlTransaction(xmlrpc_env *               const envP,
+                      CURL *                     const curlSessionP,
+                      struct curlMulti *         const curlMultiP,
+                      const xmlrpc_server_info * const serverP,
+                      xmlrpc_mem_block *         const callXmlP,
+                      xmlrpc_mem_block *         const responseXmlP,
+                      const char *               const userAgent,
+                      const struct curlSetup *   const curlSetupStuffP,
+                      rpc *                      const rpcP,
+                      curlTransaction **         const curlTransactionPP) {
+
+    curlTransaction * curlTransactionP;
+
+    MALLOCVAR(curlTransactionP);
+    if (curlTransactionP == NULL)
+        xmlrpc_faultf(envP, "No memory to create Curl transaction.");
+    else {
+        curlTransactionP->curlSessionP = curlSessionP;
+        curlTransactionP->curlMultiP   = curlMultiP;
+        curlTransactionP->rpcP         = rpcP;
+
+        curlTransactionP->serverUrl = strdup(serverP->_server_url);
+        if (curlTransactionP->serverUrl == NULL)
+            xmlrpc_faultf(envP, "Out of memory to store server URL.");
+        else {
+            createCurlHeaderList(envP, serverP, userAgent,
+                                 &curlTransactionP->headerList);
+            
+            if (!envP->fault_occurred)
+                setupCurlSession(envP, curlTransactionP,
+                                 callXmlP, responseXmlP,
+                                 curlSetupStuffP);
+
+            if (envP->fault_occurred)
+                xmlrpc_strfree(curlTransactionP->serverUrl);
+        }
+        if (envP->fault_occurred)
+            free(curlTransactionP);
+    }
+    *curlTransactionPP = curlTransactionP;
+}
+
+
+
+static void
+destroyCurlTransaction(curlTransaction * const curlTransactionP) {
+
+    curl_slist_free_all(curlTransactionP->headerList);
+    xmlrpc_strfree(curlTransactionP->serverUrl);
+
+    free(curlTransactionP);
+}
+
+
+
+static void
+getCurlTransactionError(curlTransaction * const curlTransactionP,
+                        xmlrpc_env *      const envP) {
+
+    CURLcode res;
+    long http_result;
+
+    res = curl_easy_getinfo(curlTransactionP->curlSessionP,
+                            CURLINFO_HTTP_CODE, &http_result);
+    
+    if (res != CURLE_OK)
+        xmlrpc_env_set_fault_formatted(
+            envP, XMLRPC_INTERNAL_ERROR, 
+            "Curl performed the HTTP POST request, but was "
+            "unable to say what the HTTP result code was.  "
+            "curl_easy_getinfo(CURLINFO_HTTP_CODE) says: %s", 
+            curlTransactionP->curlError);
+    else {
+        if (http_result != 200)
+            xmlrpc_env_set_fault_formatted(
+                envP, XMLRPC_NETWORK_ERROR, "HTTP response: %ld",
+                http_result);
+    }
+}
+
+
+
+static void
+performCurlTransaction(xmlrpc_env *      const envP,
+                       curlTransaction * const curlTransactionP) {
+
+    CURL * const curlSessionP = curlTransactionP->curlSessionP;
+
+    CURLcode res;
+
+    res = curl_easy_perform(curlSessionP);
+    
+    if (res != CURLE_OK)
+        xmlrpc_env_set_fault_formatted(
+            envP, XMLRPC_NETWORK_ERROR, "Curl failed to perform "
+            "HTTP POST request.  curl_easy_perform() says: %s", 
+            curlTransactionP->curlError);
+    else
+        getCurlTransactionError(curlTransactionP, envP);
+}
+
+
+
+static void
+createRpc(xmlrpc_env *                     const envP,
+          struct xmlrpc_client_transport * const clientTransportP,
+          CURL *                           const curlSessionP,
+          const xmlrpc_server_info *       const serverP,
+          xmlrpc_mem_block *               const callXmlP,
+          xmlrpc_mem_block *               const responseXmlP,
+          xmlrpc_transport_asynch_complete       complete, 
+          struct xmlrpc_call_info *        const callInfoP,
+          rpc **                           const rpcPP) {
+
+    rpc * rpcP;
+
+    MALLOCVAR(rpcP);
+    if (rpcP == NULL)
+        xmlrpc_faultf(envP, "Couldn't allocate memory for rpc object");
+    else {
+        rpcP->callInfoP    = callInfoP;
+        rpcP->complete     = complete;
+        rpcP->responseXmlP = responseXmlP;
+
+        createCurlTransaction(envP,
+                              curlSessionP,
+                              clientTransportP->curlMultiP,
+                              serverP,
+                              callXmlP, responseXmlP, 
+                              clientTransportP->userAgent,
+                              &clientTransportP->curlSetupStuff,
+                              rpcP,
+                              &rpcP->curlTransactionP);
+        if (!envP->fault_occurred) {
+            if (envP->fault_occurred)
+                destroyCurlTransaction(rpcP->curlTransactionP);
+        }
+        if (envP->fault_occurred)
+            free(rpcP);
+    }
+    *rpcPP = rpcP;
+}
+
+
+
+static void 
+destroyRpc(rpc * const rpcP) {
+
+    XMLRPC_ASSERT_PTR_OK(rpcP);
+
+    destroyCurlTransaction(rpcP->curlTransactionP);
+
+    free(rpcP);
+}
+
+
+
+static void
+performRpc(xmlrpc_env * const envP,
+           rpc *        const rpcP) {
+
+    performCurlTransaction(envP, rpcP->curlTransactionP);
+}
+
+
+
+static void
+startCurlTransaction(xmlrpc_env *      const envP,
+                     curlTransaction * const curlTransactionP) {
+
+    /* A Curl session is serial -- it processes zero or one transaction
+       at a time.  We use the "private" attribute of the Curl session to
+       indicate which transaction it is presently processing.  This is
+       important when the transaction finishes, because libcurl will just
+       tell us that something finished on a particular session, not that
+       a particular transaction finished.
+    */
+    curl_easy_setopt(curlTransactionP->curlSessionP, CURLOPT_PRIVATE,
+                     curlTransactionP);
+
+    curlMulti_addHandle(envP,
+                        curlTransactionP->curlMultiP,
+                        curlTransactionP->curlSessionP);
+}
+
+
+
+static void
+startRpc(xmlrpc_env * const envP,
+         rpc *        const rpcP) {
+
+    startCurlTransaction(envP, rpcP->curlTransactionP);
+}
+
+
+
+static void 
+sendRequest(xmlrpc_env *                     const envP, 
+            struct xmlrpc_client_transport * const clientTransportP,
+            const xmlrpc_server_info *       const serverP,
+            xmlrpc_mem_block *               const callXmlP,
+            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 Curl client transport.
+-----------------------------------------------------------------------------*/
+    rpc * rpcP;
+    xmlrpc_mem_block * responseXmlP;
+
+    responseXmlP = XMLRPC_MEMBLOCK_NEW(char, envP, 0);
+    if (!envP->fault_occurred) {
+        CURL * const curlSessionP = curl_easy_init();
+    
+        if (curlSessionP == NULL)
+            xmlrpc_faultf(envP, "Could not create Curl session.  "
+                          "curl_easy_init() failed.");
+        else {
+            createRpc(envP, clientTransportP, curlSessionP, serverP,
+                      callXmlP, responseXmlP, complete, callInfoP,
+                      &rpcP);
+            
+            if (!envP->fault_occurred) {
+                startRpc(envP, rpcP);
+                
+                if (envP->fault_occurred)
+                    destroyRpc(rpcP);
+            }
+            if (envP->fault_occurred)
+                curl_easy_cleanup(curlSessionP);
+        }
+        if (envP->fault_occurred)
+            XMLRPC_MEMBLOCK_FREE(char, responseXmlP);
+    }
+    /* If we're returning success, the user's eventual finish_asynch
+       call will destroy this RPC, Curl session, and response buffer
+       and remove the Curl session from the Curl multi manager.
+       (If we're returning failure, we didn't create any of those).
+    */
+}
+
+
+
+static void
+finishCurlTransaction(xmlrpc_env * const envP ATTR_UNUSED,
+                      CURL *       const curlSessionP,
+                      CURLcode     const result) {
+/*----------------------------------------------------------------------------
+  Handle the event that a Curl transaction has completed on the Curl
+  session identified by 'curlSessionP'.
+
+  Tell the requester of the RPC which this transaction serves the
+  results.
+
+  Remove the Curl session from its Curl multi manager and destroy the
+  Curl session, the XML response buffer, the Curl transaction, and the RPC.
+-----------------------------------------------------------------------------*/
+    curlTransaction * curlTransactionP;
+    rpc * rpcP;
+
+    curl_easy_getinfo(curlSessionP, CURLINFO_PRIVATE, &curlTransactionP);
+
+    rpcP = curlTransactionP->rpcP;
+
+    curlMulti_removeHandle(curlTransactionP->curlMultiP,
+                           curlTransactionP->curlSessionP);
+    {
+        xmlrpc_env env;
+
+        xmlrpc_env_init(&env);
+
+        if (result != CURLE_OK) {
+            xmlrpc_env_set_fault_formatted(
+                envP, XMLRPC_NETWORK_ERROR, "libcurl failed to execute the "
+                "HTTP POST transaction.  %s", curlTransactionP->curlError);
+        } else
+            getCurlTransactionError(curlTransactionP, &env);
+
+        rpcP->complete(rpcP->callInfoP, rpcP->responseXmlP, env);
+
+        xmlrpc_env_clean(&env);
+    }
+
+    curl_easy_cleanup(curlSessionP);
+
+    XMLRPC_MEMBLOCK_FREE(char, rpcP->responseXmlP);
+
+    destroyRpc(rpcP);
+}
+
+
+
+static struct timeval
+selectTimeout(xmlrpc_timeoutType const timeoutType,
+              struct timeval     const timeoutTime) {
+/*----------------------------------------------------------------------------
+   Return the value that should be used in the select() call to wait for
+   there to be work for the Curl multi manager to do, given that the user
+   wants to timeout according to 'timeoutType' and 'timeoutTime'.
+-----------------------------------------------------------------------------*/
+    unsigned int selectTimeoutMillisec;
+    struct timeval retval;
+
+    selectTimeoutMillisec = 0; // quiet compiler warning
+
+    /* We assume there is work to do at least every 3 seconds, because
+       the Curl multi manager often has retries and other scheduled work
+       that doesn't involve file handles on which we can select().
+    */
+    switch (timeoutType) {
+    case timeout_no:
+        selectTimeoutMillisec = 3000;
+        break;
+    case timeout_yes: {
+        struct timeval nowTime;
+        int timeLeft;
+
+        gettimeofday(&nowTime, NULL);
+        timeLeft = timeDiffMillisec(timeoutTime, nowTime);
+
+        selectTimeoutMillisec = MIN(3000, MAX(0, timeLeft));
+    }
+    break;
+    }
+    retval.tv_sec = selectTimeoutMillisec / 1000;
+    retval.tv_usec = (selectTimeoutMillisec % 1000) * 1000;
+
+    return retval;
+}        
+
+
+
+static void
+processCurlMessages(xmlrpc_env *       const envP,
+                    struct curlMulti * const curlMultiP) {
+        
+    bool endOfMessages;
+
+    endOfMessages = false;   /* initial assumption */
+
+    while (!endOfMessages && !envP->fault_occurred) {
+        CURLMsg curlMsg;
+
+        curlMulti_getMessage(curlMultiP, &endOfMessages, &curlMsg);
+
+        if (!endOfMessages) {
+            if (curlMsg.msg == CURLMSG_DONE)
+                finishCurlTransaction(envP, curlMsg.easy_handle,
+                                      curlMsg.data.result);
+        }
+    }
+}
+
+
+
+static void
+waitForWork(xmlrpc_env *       const envP,
+            struct curlMulti * const curlMultiP,
+            xmlrpc_timeoutType const timeoutType,
+            struct timeval     const deadline) {
+    
+    fd_set readFdSet;
+    fd_set writeFdSet;
+    fd_set exceptFdSet;
+    int maxFd;
+
+    curlMulti_fdset(envP, curlMultiP,
+                    &readFdSet, &writeFdSet, &exceptFdSet, &maxFd);
+    if (!envP->fault_occurred) {
+        if (maxFd == -1) {
+            /* There are no Curl file descriptors on which to wait.
+               So either there's work to do right now or all transactions
+               are already complete.
+            */
+        } else {
+            struct timeval selectTimeoutArg;
+            int rc;
+            
+            selectTimeoutArg = selectTimeout(timeoutType, deadline);
+
+            rc = select(maxFd+1, &readFdSet, &writeFdSet, &exceptFdSet,
+                        &selectTimeoutArg);
+            
+            if (rc < 0)
+                xmlrpc_faultf(envP, "Impossible failure of select() "
+                              "with errno %d (%s)",
+                              errno, strerror(errno));
+            else {
+                /* Believe it or not, the Curl multi manager needs the
+                   results of our select().  So hand them over:
+                */
+                curlMulti_updateFdSet(curlMultiP,
+                                      readFdSet, writeFdSet, exceptFdSet);
+            }
+        }
+    }
+}
+
+
+
+static void
+doCurlWork(xmlrpc_env *       const envP,
+           struct curlMulti * const curlMultiP,
+           bool *             const rpcStillRunningP) {
+/*----------------------------------------------------------------------------
+   Do whatever work is ready to be done by the Curl multi manager
+   identified by 'curlMultiP'.  This typically is transferring data on
+   an HTTP connection because the server is ready.
+
+   Return *rpcStillRunningP false if this work completes all of the
+   manager's transactions so that there is no reason to call us ever
+   again.
+
+   Where the multi manager completes an HTTP transaction, also complete
+   the associated RPC.
+-----------------------------------------------------------------------------*/
+    bool immediateWorkToDo;
+    int runningHandles;
+
+    immediateWorkToDo = true;  /* initial assumption */
+
+    while (immediateWorkToDo && !envP->fault_occurred) {
+        curlMulti_perform(envP, curlMultiP,
+                          &immediateWorkToDo, &runningHandles);
+    }
+
+    /* We either did all the work that's ready to do or hit an error. */
+
+    if (!envP->fault_occurred) {
+        /* The work we did may have resulted in asynchronous messages
+           (asynchronous to the thing the refer to, not to us, of course).
+           In particular the message "Curl transaction has completed".
+           So we process those now.
+        */
+        processCurlMessages(envP, curlMultiP);
+
+        *rpcStillRunningP = runningHandles > 0;
+    }
+}
+
+
+
+static void
+finishCurlSessions(xmlrpc_env *       const envP,
+                   struct curlMulti * const curlMultiP,
+                   xmlrpc_timeoutType const timeoutType,
+                   struct timeval     const deadline) {
+
+    bool rpcStillRunning;
+    bool timedOut;
+    
+    rpcStillRunning = true;  /* initial assumption */
+    timedOut = false;
+    
+    while (rpcStillRunning && !timedOut && !envP->fault_occurred) {
+        waitForWork(envP, curlMultiP, timeoutType, deadline);
+
+        if (!envP->fault_occurred) {
+            struct timeval nowTime;
+
+            doCurlWork(envP, curlMultiP, &rpcStillRunning);
+            
+            gettimeofday(&nowTime, NULL);
+            
+            timedOut = (timeoutType == timeout_yes &&
+                        timeIsAfter(nowTime, deadline));
+        }
+    }
+}
+
+
+
+static void 
+finishAsynch(
+    struct xmlrpc_client_transport * const clientTransportP,
+    xmlrpc_timeoutType               const timeoutType,
+    xmlrpc_timeout                   const timeout) {
+/*----------------------------------------------------------------------------
+   Wait for the Curl multi manager to finish the Curl transactions for
+   all outstanding RPCs and destroy those RPCs.
+
+   This does the 'finish_asynch' operation for a Curl client transport.
+
+   It would be cool to replace this with something analogous to the
+   Curl asynchronous interface: Have something like curl_multi_fdset()
+   that returns a bunch of file descriptors on which the user can wait
+   (along with possibly other file descriptors of his own) and
+   something like curl_multi_perform() to finish whatever RPCs are
+   ready to finish at that moment.  The implementation would be little
+   more than wrapping curl_multi_fdset() and curl_multi_perform().
+-----------------------------------------------------------------------------*/
+    xmlrpc_env env;
+
+    struct timeval waitTimeoutTime;
+        /* The datetime after which we should quit waiting */
+
+    xmlrpc_env_init(&env);
+    
+    if (timeoutType == timeout_yes) {
+        struct timeval waitStartTime;
+        gettimeofday(&waitStartTime, NULL);
+        addMilliseconds(waitStartTime, timeout, &waitTimeoutTime);
+    }
+
+    finishCurlSessions(&env, clientTransportP->curlMultiP,
+                       timeoutType, waitTimeoutTime);
+
+    /* If the above fails, it is catastrophic, because it means there is
+       no way to complete outstanding Curl transactions and RPCs, and
+       no way to release their resources.
+
+       We should at least expand this interface some day to push the
+       problem back up the user, but for now we just do this Hail Mary
+       response.
+
+       Note that a failure of finishCurlSessions() does not mean that
+       a session completed with an error or an RPC completed with an
+       error.  Those things are reported up through the user's 
+       xmlrpc_transport_asynch_complete routine.  A failure here is
+       something that stopped us from calling that.
+    */
+
+    if (env.fault_occurred)
+        fprintf(stderr, "finishAsync() failed.  Xmlrpc-c Curl transport "
+                "is now in an unknown state and may not be able to "
+                "continue functioning.  Specifics of the failure: %s\n",
+                env.fault_string);
+
+    xmlrpc_env_clean(&env);
+}
+
+
+
+static void
+call(xmlrpc_env *                     const envP,
+     struct xmlrpc_client_transport * const clientTransportP,
+     const xmlrpc_server_info *       const serverP,
+     xmlrpc_mem_block *               const callXmlP,
+     xmlrpc_mem_block **              const responseXmlPP) {
+
+    xmlrpc_mem_block * responseXmlP;
+    rpc * rpcP;
+
+    XMLRPC_ASSERT_ENV_OK(envP);
+    XMLRPC_ASSERT_PTR_OK(serverP);
+    XMLRPC_ASSERT_PTR_OK(callXmlP);
+    XMLRPC_ASSERT_PTR_OK(responseXmlPP);
+
+    responseXmlP = XMLRPC_MEMBLOCK_NEW(char, envP, 0);
+    if (!envP->fault_occurred) {
+        /* Only one RPC at a time can use a Curl session, so we have to
+           hold the lock as long as our RPC exists.
+        */
+        lockSyncCurlSession(clientTransportP);
+        createRpc(envP, clientTransportP, clientTransportP->syncCurlSessionP,
+                  serverP,
+                  callXmlP, responseXmlP,
+                  NULL, NULL,
+                  &rpcP);
+
+        if (!envP->fault_occurred) {
+            performRpc(envP, rpcP);
+
+            *responseXmlPP = responseXmlP;
+
+            destroyRpc(rpcP);
+        }
+        unlockSyncCurlSession(clientTransportP);
+        if (envP->fault_occurred)
+            XMLRPC_MEMBLOCK_FREE(char, responseXmlP);
+    }
+}
+
+
+
+static void
+setupGlobalConstants(xmlrpc_env * const envP) {
+/*----------------------------------------------------------------------------
+   See longwinded discussion of the global constant issue at the top of
+   this file.
+-----------------------------------------------------------------------------*/
+    initWindowsStuff(envP);
+
+    if (!envP->fault_occurred) {
+        CURLcode rc;
+
+        rc = curl_global_init(CURL_GLOBAL_ALL);
+        
+        if (rc != CURLE_OK)
+            xmlrpc_faultf(envP, "curl_global_init() failed with code %d", rc);
+    }
+}
+
+
+
+static void
+teardownGlobalConstants(void) {
+/*----------------------------------------------------------------------------
+   See longwinded discussionof the global constant issue at the top of
+   this file.
+-----------------------------------------------------------------------------*/
+    curl_global_cleanup();
+
+    termWindowsStuff();
+}
+
+
+
+struct xmlrpc_client_transport_ops xmlrpc_curl_transport_ops = {
+    &setupGlobalConstants,
+    &teardownGlobalConstants,
+    &create,
+    &destroy,
+    &sendRequest,
+    &call,
+    &finishAsynch,
+};