/*=============================================================================
                           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,
};
