initial load of upstream version 1.06.32
[xmlrpc-c] / lib / curl_transport / xmlrpc_curl_transport.c
1 /*=============================================================================
2                            xmlrpc_curl_transport
3 ===============================================================================
4    Curl-based client transport for Xmlrpc-c
5
6    By Bryan Henderson 04.12.10.
7
8    Contributed to the public domain by its author.
9 =============================================================================*/
10
11 /*----------------------------------------------------------------------------
12    Curl global variables:
13
14    Curl maintains some minor information in process-global variables.
15    One must call curl_global_init() to initialize them before calling
16    any other Curl library function.  This is not state information --
17    it is constants.  They just aren't the kind of constants that the
18    library loader knows how to set, so there has to be this explicit
19    call to set them up.  The matching function curl_global_cleanup()
20    returns resources these use (to wit, the constants live in
21    malloc'ed storage and curl_global_cleanup() frees the storage).
22
23    So our setup_global_const transport operation calls
24    curl_global_init() and our teardown_global_const calls
25    curl_global_cleanup().
26
27    The Curl library is supposed to maintain a reference count for the
28    global constants so that multiple modules using the library and
29    independently calling curl_global_init() and curl_global_cleanup()
30    are not a problem.  But today, it just keeps a flag "I have been
31    initialized" and the first call to curl_global_cleanup() destroys
32    the constants for everybody.  Therefore, the user of the Xmlrpc-c
33    Curl client XML transport must make sure not to call
34    teardownGlobalConstants until everything else in his program is
35    done using the Curl library.
36
37    Note that curl_global_init() is not threadsafe (with or without the
38    reference count), therefore our setup_global_const is not, and must
39    be called when no other thread in the process is running.
40    Typically, one calls it right at the beginning of the program.
41
42    There are actually two other classes of global variables in the
43    Curl library, which we are ignoring: debug options and custom
44    memory allocator function identities.  Our code never changes these
45    global variables from default.  If something else in the user's
46    program does, User is responsible for making sure it doesn't
47    interfere with our use of the library.
48
49    Note that when we say what the Curl library does, we're also
50    talking about various other libraries Curl uses internally, and in
51    fact much of what we're saying about global variables springs from
52    such subordinate libraries as OpenSSL and Winsock.
53 -----------------------------------------------------------------------------*/
54
55 #include <string.h>
56 #include <stdlib.h>
57 #include <errno.h>
58 #include <assert.h>
59 #include <sys/time.h>
60
61 #include "xmlrpc_config.h"
62
63 #include "bool.h"
64 #include "girmath.h"
65 #include "mallocvar.h"
66 #include "linklist.h"
67 #include "girstring.h"
68 #include "pthreadx.h"
69
70 #include "xmlrpc-c/base.h"
71 #include "xmlrpc-c/base_int.h"
72 #include "xmlrpc-c/string_int.h"
73 #include "xmlrpc-c/client.h"
74 #include "xmlrpc-c/client_int.h"
75 #include "xmlrpc-c/transport.h"
76 #include "version.h"
77
78 #include <curl/curl.h>
79 #include <curl/types.h>
80 #include <curl/easy.h>
81 #include <curl/multi.h>
82
83 #if defined (WIN32) && defined(_DEBUG)
84 #  include <crtdbg.h>
85 #  define new DEBUG_NEW
86 #  define malloc(size) _malloc_dbg( size, _NORMAL_BLOCK, __FILE__, __LINE__)
87 #  undef THIS_FILE
88    static char THIS_FILE[] = __FILE__;
89 #endif /*WIN32 && _DEBUG*/
90
91
92
93 struct curlSetup {
94
95     /* This is all client transport properties that are implemented as
96        simple Curl session properties (i.e. the transport basically just
97        passes them through to Curl without looking at them).
98
99        People occasionally want to replace all this with something where
100        the Xmlrpc-c user simply does the curl_easy_setopt() call and this
101        code need not know about all these options.  Unfortunately, that's
102        a significant modularity violation.  Either the Xmlrpc-c user
103        controls the Curl object or he doesn't.  If he does, then he
104        shouldn't use libxmlrpc_client -- he should just copy some of this
105        code into his own program.  If he doesn't, then he should never see
106        the Curl library.
107
108        Speaking of modularity: the only reason this is a separate struct
109        is to make the code easier to manage.  Ideally, the fact that these
110        particular properties of the transport are implemented by simple
111        Curl session setup would be known only at the lowest level code
112        that does that setup.
113     */
114
115     const char * networkInterface;
116         /* This identifies the network interface on the local side to
117            use for the session.  It is an ASCIIZ string in the form
118            that the Curl recognizes for setting its CURLOPT_INTERFACE
119            option (also the --interface option of the Curl program).
120            E.g. "9.1.72.189" or "giraffe-data.com" or "eth0".  
121
122            It isn't necessarily valid, but it does have a terminating NUL.
123
124            NULL means we have no preference.
125         */
126     xmlrpc_bool sslVerifyPeer;
127         /* In an SSL connection, we should authenticate the server's SSL
128            certificate -- refuse to talk to him if it isn't authentic.
129            This is equivalent to Curl's CURLOPT_SSL_VERIFY_PEER option.
130         */
131     xmlrpc_bool sslVerifyHost;
132         /* In an SSL connection, we should verify that the server's
133            certificate (independently of whether the certificate is
134            authentic) indicates the host name that is in the URL we
135            are using for the server.
136         */
137
138     const char * sslCert;
139     const char * sslCertType;
140     const char * sslCertPasswd;
141     const char * sslKey;
142     const char * sslKeyType;
143     const char * sslKeyPasswd;
144     const char * sslEngine;
145     bool         sslEngineDefault;
146     unsigned int sslVersion;
147     const char * caInfo;
148     const char * caPath;
149     const char * randomFile;
150     const char * egdSocket;
151     const char * sslCipherList;
152 };
153
154
155 /*============================================================================
156       locks
157 ==============================================================================
158    This is the beginnings of a lock abstraction that will allow this
159    transport to be used with locks other than pthread locks
160 ============================================================================*/
161
162 struct lock {
163     pthread_mutex_t theLock;
164     void (*lock)(struct lock *);
165     void (*unlock)(struct lock *);
166     void (*destroy)(struct lock *);
167 };
168
169 typedef struct lock lock;
170
171 static void
172 lock_pthread(struct lock * const lockP) {
173     pthread_mutex_lock(&lockP->theLock);
174 }
175
176 static void
177 unlock_pthread(struct lock * const lockP) {
178     pthread_mutex_unlock(&lockP->theLock);
179 }
180
181 static void
182 destroyLock_pthread(struct lock * const lockP) {
183     pthread_mutex_destroy(&lockP->theLock);
184     free(lockP);
185 }
186
187
188 static struct lock *
189 createLock_pthread(void) {
190     struct lock * lockP;
191     MALLOCVAR(lockP);
192     if (lockP) {
193         pthread_mutex_init(&lockP->theLock, NULL);
194         lockP->lock    = &lock_pthread;
195         lockP->unlock  = &unlock_pthread;
196         lockP->destroy = &destroyLock_pthread;
197     }
198     return lockP;
199 }
200
201
202
203
204 /*=============================================================================
205     curlMulti
206 =============================================================================*/
207
208 struct curlMulti {
209 /*----------------------------------------------------------------------------
210    This is an extension to Curl's CURLM object.  The extensions are:
211
212    1) It has a lock so multiple threads can use it simultaneously.
213
214    2) Its "select" file descriptor vectors are self-contained.  CURLM
215       requires the user to maintain them separately.
216 -----------------------------------------------------------------------------*/
217     CURLM * curlMultiP;
218     lock * lockP;
219         /* Hold this lock while accessing or using *curlMultiP.  You're
220            using the multi manager whenever you're calling a Curl
221            library multi manager function.
222         */
223     /* The following file descriptor sets are an integral part of the
224        CURLM object; Our curlMulti_fdset() routine binds them to the
225        CURLM object, and said object expects us to use them in a very
226        specific way, including doing a select() on them.  It is very,
227        very messy.
228     */
229     fd_set readFdSet;
230     fd_set writeFdSet;
231     fd_set exceptFdSet;
232 };
233
234
235
236 static struct curlMulti *
237 createCurlMulti(void) {
238
239     struct curlMulti * retval;
240     struct curlMulti * curlMultiP;
241
242     MALLOCVAR(curlMultiP);
243
244     if (curlMultiP == NULL)
245         retval = NULL;
246     else {
247         curlMultiP->lockP = createLock_pthread();
248
249         if (curlMultiP->lockP == NULL)
250             retval = NULL;
251         else {
252             curlMultiP->curlMultiP = curl_multi_init();
253             if (curlMultiP->curlMultiP == NULL)
254                 retval = NULL;
255             else
256                 retval = curlMultiP;
257
258             if (retval == NULL)
259                 curlMultiP->lockP->destroy(curlMultiP->lockP);
260         }
261         if (retval == NULL)
262             free(curlMultiP);
263     }
264     return retval;
265 }
266
267
268
269 static void
270 destroyCurlMulti(struct curlMulti * const curlMultiP) {
271
272     curl_multi_cleanup(curlMultiP->curlMultiP);
273     
274     curlMultiP->lockP->destroy(curlMultiP->lockP);
275
276     free(curlMultiP);
277 }
278
279
280
281 static void
282 curlMulti_perform(xmlrpc_env *       const envP,
283                   struct curlMulti * const curlMultiP,
284                   bool *             const immediateWorkToDoP,
285                   int *              const runningHandlesP) {
286
287     CURLMcode rc;
288
289     curlMultiP->lockP->lock(curlMultiP->lockP);
290
291     rc = curl_multi_perform(curlMultiP->curlMultiP, runningHandlesP);
292
293     curlMultiP->lockP->unlock(curlMultiP->lockP);
294
295     if (rc == CURLM_CALL_MULTI_PERFORM) {
296         *immediateWorkToDoP = true;
297     } else {
298         *immediateWorkToDoP = false;
299
300         if (rc != CURLM_OK) {
301             xmlrpc_faultf(envP,
302                           "Impossible failure of curl_multi_perform() "
303                           "with rc %d", rc);
304         }
305     }
306 }        
307
308
309
310 static void
311 curlMulti_addHandle(xmlrpc_env *       const envP,
312                     struct curlMulti * const curlMultiP,
313                     CURL *             const curlSessionP) {
314
315     CURLMcode rc;
316
317     curlMultiP->lockP->lock(curlMultiP->lockP);
318
319     rc = curl_multi_add_handle(curlMultiP->curlMultiP, curlSessionP);
320     
321     curlMultiP->lockP->unlock(curlMultiP->lockP);
322
323     if (rc != CURLM_OK)
324         xmlrpc_faultf(envP, "Could not add Curl session to the "
325                       "curl multi manager.  curl_multi_add_handle() "
326                       "returns error code %d", rc);
327 }
328
329
330 static void
331 curlMulti_removeHandle(struct curlMulti * const curlMultiP,
332                        CURL *             const curlSessionP) {
333
334     curlMultiP->lockP->lock(curlMultiP->lockP);
335
336     curl_multi_remove_handle(curlMultiP->curlMultiP, curlSessionP);
337     
338     curlMultiP->lockP->unlock(curlMultiP->lockP);
339 }
340
341
342
343 static void
344 curlMulti_getMessage(struct curlMulti * const curlMultiP,
345                      bool *             const endOfMessagesP,
346                      CURLMsg *          const curlMsgP) {
347 /*----------------------------------------------------------------------------
348    Get the next message from the queue of things the Curl multi manager
349    wants to say to us.
350
351    Return the message as *curlMsgP.
352
353    Iff there are no messages in the queue, return *endOfMessagesP == true.
354 -----------------------------------------------------------------------------*/
355     int remainingMsgCount;
356     CURLMsg * privateCurlMsgP;
357         /* Note that this is a pointer into the multi manager's memory,
358            so we have to use it under lock.
359         */
360
361     curlMultiP->lockP->lock(curlMultiP->lockP);
362     
363     privateCurlMsgP = curl_multi_info_read(curlMultiP->curlMultiP,
364                                            &remainingMsgCount);
365         
366     if (privateCurlMsgP == NULL)
367         *endOfMessagesP = true;
368     else {
369         *endOfMessagesP = false;
370         *curlMsgP = *privateCurlMsgP;
371     }    
372     curlMultiP->lockP->unlock(curlMultiP->lockP);
373 }
374
375
376
377 static void
378 curlMulti_fdset(xmlrpc_env *       const envP,
379                 struct curlMulti * const curlMultiP,
380                 fd_set *           const readFdSetP,
381                 fd_set *           const writeFdSetP,
382                 fd_set *           const exceptFdSetP,
383                 int *              const maxFdP) {
384 /*----------------------------------------------------------------------------
385    Set the CURLM object's file descriptor sets to those in the
386    curlMulti object, update those file descriptor sets with the
387    current needs of the multi manager, and return the resulting values
388    of the file descriptor sets.
389
390    This is a bizarre operation, but is necessary because of the nonmodular
391    way in which the Curl multi interface works with respect to waiting
392    for work with select().
393 -----------------------------------------------------------------------------*/
394     CURLMcode rc;
395     
396     curlMultiP->lockP->lock(curlMultiP->lockP);
397
398     /* curl_multi_fdset() doesn't _set_ the fdsets.  It adds to existing
399        ones (so you can easily do a select() on other fds and Curl
400        fds at the same time).  So we have to clear first:
401     */
402     FD_ZERO(&curlMultiP->readFdSet);
403     FD_ZERO(&curlMultiP->writeFdSet);
404     FD_ZERO(&curlMultiP->exceptFdSet);
405
406     /* WARNING: curl_multi_fdset() doesn't just update the fdsets pointed
407        to by its arguments.  It makes the CURLM object remember those
408        pointers and refer back to them later!  In fact, curl_multi_perform
409        expects its caller to have done a select() on those masks.  No,
410        really.  The man page even admits it.
411     */
412
413     rc = curl_multi_fdset(curlMultiP->curlMultiP,
414                           &curlMultiP->readFdSet,
415                           &curlMultiP->writeFdSet,
416                           &curlMultiP->exceptFdSet,
417                           maxFdP);
418
419     *readFdSetP   = curlMultiP->readFdSet;
420     *writeFdSetP  = curlMultiP->writeFdSet;
421     *exceptFdSetP = curlMultiP->exceptFdSet;
422
423     curlMultiP->lockP->unlock(curlMultiP->lockP);
424
425     if (rc != CURLM_OK)
426         xmlrpc_faultf(envP, "Impossible failure of curl_multi_fdset() "
427                       "with rc %d", rc);
428 }
429
430
431
432 static void
433 curlMulti_updateFdSet(struct curlMulti * const curlMultiP,
434                       fd_set             const readFdSet,
435                       fd_set             const writeFdSet,
436                       fd_set             const exceptFdSet) {
437 /*----------------------------------------------------------------------------
438    curl_multi_perform() expects the file descriptor sets, which were bound
439    to the CURLM object via a prior curlMulti_fdset(), to contain the results
440    of a recent select().  This subroutine provides you a way to supply those.
441 -----------------------------------------------------------------------------*/
442     curlMultiP->readFdSet   = readFdSet;
443     curlMultiP->writeFdSet  = writeFdSet;
444     curlMultiP->exceptFdSet = exceptFdSet;
445 }
446
447                       
448
449 /*===========================================================================*/
450
451
452 struct xmlrpc_client_transport {
453     struct curlMulti * curlMultiP;
454         /* The Curl multi manager that this transport uses to handle
455            multiple Curl sessions at the same time.
456         */
457     CURL * syncCurlSessionP;
458         /* Handle for a Curl library session object that we use for
459            all synchronous RPCs.  An async RPC has one of its own,
460            and consequently does not share things such as persistent
461            connections and cookies with any other RPC.
462         */
463     lock * syncCurlSessionLockP;
464         /* Hold this lock while accessing or using *syncCurlSessionP.
465            You're using the session from the time you set any
466            attributes in it or start a transaction with it until any
467            transaction has finished and you've lost interest in any
468            attributes of the session.
469         */
470     const char * userAgent;
471         /* Prefix for the User-Agent HTTP header, reflecting facilities
472            outside of Xmlrpc-c.  The actual User-Agent header consists
473            of this prefix plus information about Xmlrpc-c.  NULL means
474            none.
475
476            This is constant.
477         */
478     struct curlSetup curlSetupStuff;
479         /* This is constant */
480 };
481
482 typedef struct {
483     /* This is all stuff that really ought to be in a Curl object, but
484        the Curl library is a little too simple for that.  So we build
485        a layer on top of Curl, and define this "transaction," as an
486        object subordinate to a Curl "session."  A Curl session has
487        zero or one transactions in progress.  The Curl session
488        "private data" is a pointer to the CurlTransaction object for
489        the current transaction.
490     */
491     CURL * curlSessionP;
492         /* Handle for the Curl session that hosts this transaction.
493            Note that only one transaction at a time can use a particular
494            Curl session, so this had better not be a session that some other
495            transaction is using simultaneously.
496         */
497     struct curlMulti * curlMultiP;
498         /* The Curl multi manager which manages the above curl session,
499            if any.  An asynchronous process uses a Curl multi manager
500            to manage the in-progress Curl sessions and thereby in-progress
501            RPCs.  A synchronous process has no need of a Curl multi manager.
502         */
503     struct rpc * rpcP;
504         /* The RPC which this transaction serves.  (If this structure
505            were a true extension of the Curl library as described above,
506            this would be a void *, since the Curl library doesn't know what
507            an RPC is, but since we use it only for that, we might as well
508            use the specific type here).
509         */
510     char curlError[CURL_ERROR_SIZE];
511         /* Error message from Curl */
512     struct curl_slist * headerList;
513         /* The HTTP headers for the transaction */
514     const char * serverUrl;  /* malloc'ed - belongs to this object */
515 } curlTransaction;
516
517
518
519 typedef struct rpc {
520     curlTransaction * curlTransactionP;
521         /* The object which does the HTTP transaction, with no knowledge
522            of XML-RPC or Xmlrpc-c.
523         */
524     xmlrpc_mem_block * responseXmlP;
525         /* Where the response XML for this RPC should go or has gone. */
526     xmlrpc_transport_asynch_complete complete;
527         /* Routine to call to complete the RPC after it is complete HTTP-wise.
528            NULL if none.
529         */
530     struct xmlrpc_call_info * callInfoP;
531         /* User's identifier for this RPC */
532 } rpc;
533
534
535 static int
536 timeDiffMillisec(struct timeval const minuend,
537                  struct timeval const subtractor) {
538
539     return (minuend.tv_sec - subtractor.tv_sec) * 1000 +
540         (minuend.tv_usec - subtractor.tv_usec + 500) / 1000;
541 }
542
543
544
545 static bool
546 timeIsAfter(struct timeval const comparator,
547             struct timeval const comparand) {
548
549     if (comparator.tv_sec > comparand.tv_sec)
550         return true;
551     else if (comparator.tv_sec < comparand.tv_sec)
552         return false;
553     else {
554         /* Seconds are equal */
555         if (comparator.tv_usec > comparand.tv_usec)
556             return true;
557         else
558             return false;
559     }
560 }
561
562
563
564 static void
565 addMilliseconds(struct timeval   const addend,
566                 unsigned int     const adder,
567                 struct timeval * const sumP) {
568
569     unsigned int newRawUsec;
570
571     newRawUsec = addend.tv_usec + adder * 1000;
572
573     sumP->tv_sec  = addend.tv_sec + newRawUsec / 1000000;
574     sumP->tv_usec = newRawUsec % 1000000;
575 }
576
577
578
579 static void
580 lockSyncCurlSession(struct xmlrpc_client_transport * const transportP) {
581     transportP->syncCurlSessionLockP->lock(transportP->syncCurlSessionLockP);
582 }
583
584
585
586 static void
587 unlockSyncCurlSession(struct xmlrpc_client_transport * const transportP) {
588     transportP->syncCurlSessionLockP->unlock(transportP->syncCurlSessionLockP);
589 }
590
591
592
593 static size_t 
594 collect(void *  const ptr, 
595         size_t  const size, 
596         size_t  const nmemb,  
597         FILE  * const stream) {
598 /*----------------------------------------------------------------------------
599    This is a Curl output function.  Curl calls this to deliver the
600    HTTP response body.  Curl thinks it's writing to a POSIX stream.
601 -----------------------------------------------------------------------------*/
602     xmlrpc_mem_block * const responseXmlP = (xmlrpc_mem_block *) stream;
603     char * const buffer = ptr;
604     size_t const length = nmemb * size;
605
606     size_t retval;
607     xmlrpc_env env;
608
609     xmlrpc_env_init(&env);
610     xmlrpc_mem_block_append(&env, responseXmlP, buffer, length);
611     if (env.fault_occurred)
612         retval = (size_t)-1;
613     else
614         /* Really?  Shouldn't it be like fread() and return 'nmemb'? */
615         retval = length;
616     
617     return retval;
618 }
619
620
621
622 static void
623 initWindowsStuff(xmlrpc_env * const envP ATTR_UNUSED) {
624
625 #if defined (WIN32)
626     /* This is CRITICAL so that cURL-Win32 works properly! */
627     
628     /* So this commenter says, but I wonder why.  libcurl should do the
629        required WSAStartup() itself, and it looks to me like it does.
630        -Bryan 06.01.01
631     */
632     WORD wVersionRequested;
633     WSADATA wsaData;
634     int err;
635     wVersionRequested = MAKEWORD(1, 1);
636     
637     err = WSAStartup(wVersionRequested, &wsaData);
638     if (err)
639         xmlrpc_env_set_fault_formatted(
640             envP, XMLRPC_INTERNAL_ERROR,
641             "Winsock startup failed.  WSAStartup returned rc %d", err);
642     else {
643         if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
644             /* Tell the user that we couldn't find a useable */ 
645             /* winsock.dll. */ 
646             xmlrpc_env_set_fault_formatted(
647                 envP, XMLRPC_INTERNAL_ERROR, "Winsock reported that "
648                 "it does not implement the requested version 1.1.");
649         }
650         if (envP->fault_occurred)
651             WSACleanup();
652     }
653 #endif
654 }
655
656
657
658 static void
659 termWindowsStuff(void) {
660
661 #if defined (WIN32)
662     WSACleanup();
663 #endif
664 }
665
666
667
668 static void
669 getXportParms(xmlrpc_env *  const envP ATTR_UNUSED,
670               const struct xmlrpc_curl_xportparms * const curlXportParmsP,
671               size_t        const parmSize,
672               struct xmlrpc_client_transport * const transportP) {
673 /*----------------------------------------------------------------------------
674    Get the parameters out of *curlXportParmsP and update *transportP
675    to reflect them.
676
677    *curlXportParmsP is a 'parmSize' bytes long prefix of
678    struct xmlrpc_curl_xportparms.
679
680    curlXportParmsP is something the user created.  It's designed to be
681    friendly to the user, not to this program, and is encumbered by
682    lots of backward compatibility constraints.  In particular, the
683    user may have coded and/or compiled it at a time that struct
684    xmlrpc_curl_xportparms was smaller than it is now!
685
686    So that's why we don't simply attach a copy of *curlXportParmsP to
687    *transportP.
688
689    To the extent that *curlXportParmsP is too small to contain a parameter,
690    we return the default value for that parameter.
691
692    Special case:  curlXportParmsP == NULL means there is no input at all.
693    In that case, we return default values for everything.
694 -----------------------------------------------------------------------------*/
695     struct curlSetup * const curlSetupP = &transportP->curlSetupStuff;
696
697     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(user_agent))
698         transportP->userAgent = NULL;
699     else if (curlXportParmsP->user_agent == NULL)
700         transportP->userAgent = NULL;
701     else
702         transportP->userAgent = strdup(curlXportParmsP->user_agent);
703     
704     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(network_interface))
705         curlSetupP->networkInterface = NULL;
706     else if (curlXportParmsP->network_interface == NULL)
707         curlSetupP->networkInterface = NULL;
708     else
709         curlSetupP->networkInterface =
710             strdup(curlXportParmsP->network_interface);
711
712     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(no_ssl_verifypeer))
713         curlSetupP->sslVerifyPeer = true;
714     else
715         curlSetupP->sslVerifyPeer = !curlXportParmsP->no_ssl_verifypeer;
716         
717     if (!curlXportParmsP || 
718         parmSize < XMLRPC_CXPSIZE(no_ssl_verifyhost))
719         curlSetupP->sslVerifyHost = true;
720     else
721         curlSetupP->sslVerifyHost = !curlXportParmsP->no_ssl_verifyhost;
722
723     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(ssl_cert))
724         curlSetupP->sslCert = NULL;
725     else if (curlXportParmsP->ssl_cert == NULL)
726         curlSetupP->sslCert = NULL;
727     else
728         curlSetupP->sslCert = strdup(curlXportParmsP->ssl_cert);
729     
730     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslcerttype))
731         curlSetupP->sslCertType = NULL;
732     else if (curlXportParmsP->sslcerttype == NULL)
733         curlSetupP->sslCertType = NULL;
734     else
735         curlSetupP->sslCertType = strdup(curlXportParmsP->sslcerttype);
736     
737     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslcertpasswd))
738         curlSetupP->sslCertPasswd = NULL;
739     else if (curlXportParmsP->sslcertpasswd == NULL)
740         curlSetupP->sslCertPasswd = NULL;
741     else
742         curlSetupP->sslCertPasswd = strdup(curlXportParmsP->sslcertpasswd);
743     
744     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslkey))
745         curlSetupP->sslKey = NULL;
746     else if (curlXportParmsP->sslkey == NULL)
747         curlSetupP->sslKey = NULL;
748     else
749         curlSetupP->sslKey = strdup(curlXportParmsP->sslkey);
750     
751     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslkeytype))
752         curlSetupP->sslKeyType = NULL;
753     else if (curlXportParmsP->sslkeytype == NULL)
754         curlSetupP->sslKeyType = NULL;
755     else
756         curlSetupP->sslKeyType = strdup(curlXportParmsP->sslkeytype);
757     
758         if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslkeypasswd))
759         curlSetupP->sslKeyPasswd = NULL;
760     else if (curlXportParmsP->sslkeypasswd == NULL)
761         curlSetupP->sslKeyPasswd = NULL;
762     else
763         curlSetupP->sslKeyPasswd = strdup(curlXportParmsP->sslkeypasswd);
764     
765     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslengine))
766         curlSetupP->sslEngine = NULL;
767     else if (curlXportParmsP->sslengine == NULL)
768         curlSetupP->sslEngine = NULL;
769     else
770         curlSetupP->sslEngine = strdup(curlXportParmsP->sslengine);
771     
772     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslengine_default))
773         curlSetupP->sslEngineDefault = false;
774     else
775         curlSetupP->sslEngineDefault = !!curlXportParmsP->sslengine_default;
776     
777     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslversion))
778         curlSetupP->sslVersion = XMLRPC_SSLVERSION_DEFAULT;
779     else
780         curlSetupP->sslVersion = curlXportParmsP->sslversion;
781     
782     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(cainfo))
783         curlSetupP->caInfo = NULL;
784     else if (curlXportParmsP->cainfo == NULL)
785         curlSetupP->caInfo = NULL;
786     else
787         curlSetupP->caInfo = strdup(curlXportParmsP->cainfo);
788     
789     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(capath))
790         curlSetupP->caPath = NULL;
791     else if (curlXportParmsP->capath == NULL)
792         curlSetupP->caPath = NULL;
793     else
794         curlSetupP->caPath = strdup(curlXportParmsP->capath);
795     
796     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(randomfile))
797         curlSetupP->randomFile = NULL;
798     else if (curlXportParmsP->randomfile == NULL)
799         curlSetupP->randomFile = NULL;
800     else
801         curlSetupP->randomFile = strdup(curlXportParmsP->randomfile);
802     
803     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(egdsocket))
804         curlSetupP->egdSocket = NULL;
805     else if (curlXportParmsP->egdsocket == NULL)
806         curlSetupP->egdSocket = NULL;
807     else
808         curlSetupP->egdSocket = strdup(curlXportParmsP->egdsocket);
809     
810     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(ssl_cipher_list))
811         curlSetupP->sslCipherList = NULL;
812     else if (curlXportParmsP->ssl_cipher_list == NULL)
813         curlSetupP->sslCipherList = NULL;
814     else
815         curlSetupP->sslCipherList = strdup(curlXportParmsP->ssl_cipher_list);
816
817 }
818
819
820
821 static void
822 freeXportParms(const struct xmlrpc_client_transport * const transportP) {
823
824     const struct curlSetup * const curlSetupP = &transportP->curlSetupStuff;
825
826     if (curlSetupP->sslCipherList)
827         xmlrpc_strfree(curlSetupP->sslCipherList);
828     if (curlSetupP->egdSocket)
829         xmlrpc_strfree(curlSetupP->egdSocket);
830     if (curlSetupP->randomFile)
831         xmlrpc_strfree(curlSetupP->randomFile);
832     if (curlSetupP->caPath)
833         xmlrpc_strfree(curlSetupP->caPath);
834     if (curlSetupP->caInfo)
835         xmlrpc_strfree(curlSetupP->caInfo);
836     if (curlSetupP->sslEngine)
837         xmlrpc_strfree(curlSetupP->sslEngine);
838     if (curlSetupP->sslKeyPasswd)
839         xmlrpc_strfree(curlSetupP->sslKeyPasswd);
840     if (curlSetupP->sslKeyType)
841         xmlrpc_strfree(curlSetupP->sslKeyType);
842     if (curlSetupP->sslKey)
843         xmlrpc_strfree(curlSetupP->sslKey);
844     if (curlSetupP->sslCertPasswd)
845         xmlrpc_strfree(curlSetupP->sslCertPasswd);
846     if (curlSetupP->sslCertType)
847         xmlrpc_strfree(curlSetupP->sslCertType);
848     if (curlSetupP->sslCert)
849         xmlrpc_strfree(curlSetupP->sslCert);
850     if (curlSetupP->networkInterface)
851         xmlrpc_strfree(curlSetupP->networkInterface);
852     if (transportP->userAgent)
853         xmlrpc_strfree(transportP->userAgent);
854 }
855
856
857
858 static void
859 createSyncCurlSession(xmlrpc_env * const envP,
860                       CURL **      const curlSessionPP) {
861 /*----------------------------------------------------------------------------
862    Create a Curl session to be used for multiple serial transactions.
863    The Curl session we create is not complete -- it still has to be
864    further set up for each particular transaction.
865
866    We can't set up anything here that changes from one transaction to the
867    next.
868
869    We don't bother setting up anything that has to be set up for an
870    asynchronous transaction because code that is common between synchronous
871    and asynchronous transactions takes care of that anyway.
872
873    That leaves things, such as cookies, that don't exist for
874    asynchronous transactions, and are common to multiple serial
875    synchronous transactions.
876 -----------------------------------------------------------------------------*/
877     CURL * const curlSessionP = curl_easy_init();
878
879     if (curlSessionP == NULL)
880         xmlrpc_faultf(envP, "Could not create Curl session.  "
881                       "curl_easy_init() failed.");
882     else {
883         /* The following is a trick.  CURLOPT_COOKIEFILE is the name
884            of the file containing the initial cookies for the Curl
885            session.  But setting it is also what turns on the cookie
886            function itself, whereby the Curl library accepts and
887            stores cookies from the server and sends them back on
888            future requests.  We don't have a file of initial cookies, but
889            we want to turn on cookie function, so we set the option to
890            something we know does not validly name a file.  Curl will
891            ignore the error and just start up cookie function with no
892            initial cookies.
893         */
894         curl_easy_setopt(curlSessionP, CURLOPT_COOKIEFILE, "");
895
896         *curlSessionPP = curlSessionP;
897     }
898 }
899
900
901
902 static void
903 destroySyncCurlSession(CURL * const curlSessionP) {
904
905     curl_easy_cleanup(curlSessionP);
906 }
907
908
909
910 static void
911 makeSyncCurlSession(xmlrpc_env *                     const envP,
912                     struct xmlrpc_client_transport * const transportP) {
913
914     transportP->syncCurlSessionLockP = createLock_pthread();
915     if (transportP->syncCurlSessionLockP == NULL)
916         xmlrpc_faultf(envP, "Unable to create lock for "
917                       "synchronous Curl session.");
918     else {
919         createSyncCurlSession(envP, &transportP->syncCurlSessionP);
920         if (envP->fault_occurred)
921             transportP->syncCurlSessionLockP->destroy(
922                 transportP->syncCurlSessionLockP); 
923     }
924 }
925
926
927
928 static void
929 unmakeSyncCurlSession(struct xmlrpc_client_transport * const transportP) {
930
931     destroySyncCurlSession(transportP->syncCurlSessionP);
932
933     transportP->syncCurlSessionLockP->destroy(
934         transportP->syncCurlSessionLockP); 
935 }
936
937
938
939 static void
940 assertConstantsMatch(void) {
941 /*----------------------------------------------------------------------------
942    There are some constants that we define as part of the Xmlrpc-c
943    interface that are identical to constants in the Curl interface to
944    make curl option setting work.  This function asserts such
945    formally.
946 -----------------------------------------------------------------------------*/
947     assert(XMLRPC_SSLVERSION_DEFAULT == CURL_SSLVERSION_DEFAULT);
948     assert(XMLRPC_SSLVERSION_TLSv1   == CURL_SSLVERSION_TLSv1);
949     assert(XMLRPC_SSLVERSION_SSLv2   == CURL_SSLVERSION_SSLv2);
950     assert(XMLRPC_SSLVERSION_SSLv3   == CURL_SSLVERSION_SSLv3);
951 }
952
953
954
955 static void 
956 create(xmlrpc_env *                      const envP,
957        int                               const flags ATTR_UNUSED,
958        const char *                      const appname ATTR_UNUSED,
959        const char *                      const appversion ATTR_UNUSED,
960        const struct xmlrpc_xportparms *  const transportparmsP,
961        size_t                            const parm_size,
962        struct xmlrpc_client_transport ** const handlePP) {
963 /*----------------------------------------------------------------------------
964    This does the 'create' operation for a Curl client transport.
965 -----------------------------------------------------------------------------*/
966     struct xmlrpc_curl_xportparms * const curlXportParmsP = 
967         (struct xmlrpc_curl_xportparms *) transportparmsP;
968
969     struct xmlrpc_client_transport * transportP;
970
971     assertConstantsMatch();
972
973     MALLOCVAR(transportP);
974     if (transportP == NULL)
975         xmlrpc_faultf(envP, "Unable to allocate transport descriptor.");
976     else {
977         transportP->curlMultiP = createCurlMulti();
978         
979         if (transportP->curlMultiP == NULL)
980             xmlrpc_faultf(envP, "Unable to create Curl multi manager");
981         else {
982             getXportParms(envP, curlXportParmsP, parm_size, transportP);
983
984             if (!envP->fault_occurred) {
985                 makeSyncCurlSession(envP, transportP);
986
987                 if (envP->fault_occurred)
988                     freeXportParms(transportP);
989             }
990             if (envP->fault_occurred)
991                 destroyCurlMulti(transportP->curlMultiP);
992         }                 
993         if (envP->fault_occurred)
994             free(transportP);
995     }
996     *handlePP = transportP;
997 }
998
999
1000
1001 static void
1002 assertNoOutstandingCurlWork(struct curlMulti * const curlMultiP) {
1003
1004     xmlrpc_env env;
1005     bool immediateWorkToDo;
1006     int runningHandles;
1007     
1008     xmlrpc_env_init(&env);
1009     
1010     curlMulti_perform(&env, curlMultiP, &immediateWorkToDo, &runningHandles);
1011     
1012     /* We know the above was a no-op, since we're asserting that there
1013        is no outstanding work.
1014     */
1015     XMLRPC_ASSERT(!env.fault_occurred);
1016     XMLRPC_ASSERT(!immediateWorkToDo);
1017     XMLRPC_ASSERT(runningHandles == 0);
1018     xmlrpc_env_clean(&env);
1019 }
1020
1021
1022
1023 static void 
1024 destroy(struct xmlrpc_client_transport * const clientTransportP) {
1025 /*----------------------------------------------------------------------------
1026    This does the 'destroy' operation for a Curl client transport.
1027 -----------------------------------------------------------------------------*/
1028     XMLRPC_ASSERT(clientTransportP != NULL);
1029
1030     assertNoOutstandingCurlWork(clientTransportP->curlMultiP);
1031         /* We know this is true because a condition of destroying the
1032            transport is that there be no outstanding RPCs.
1033         */
1034     unmakeSyncCurlSession(clientTransportP);
1035
1036     destroyCurlMulti(clientTransportP->curlMultiP);
1037
1038     freeXportParms(clientTransportP);
1039
1040     free(clientTransportP);
1041 }
1042
1043
1044
1045 static void
1046 addHeader(xmlrpc_env * const envP,
1047           struct curl_slist ** const headerListP,
1048           const char *         const headerText) {
1049
1050     struct curl_slist * newHeaderList;
1051     newHeaderList = curl_slist_append(*headerListP, headerText);
1052     if (newHeaderList == NULL)
1053         xmlrpc_faultf(envP,
1054                       "Could not add header '%s'.  "
1055                       "curl_slist_append() failed.", headerText);
1056     else
1057         *headerListP = newHeaderList;
1058 }
1059
1060
1061
1062 static void
1063 addContentTypeHeader(xmlrpc_env *         const envP,
1064                      struct curl_slist ** const headerListP) {
1065     
1066     addHeader(envP, headerListP, "Content-Type: text/xml");
1067 }
1068
1069
1070
1071 static void
1072 addUserAgentHeader(xmlrpc_env *         const envP,
1073                    struct curl_slist ** const headerListP,
1074                    const char *         const userAgent) {
1075     
1076     if (userAgent) {
1077         curl_version_info_data * const curlInfoP =
1078             curl_version_info(CURLVERSION_NOW);
1079         char curlVersion[32];
1080         const char * userAgentHeader;
1081         
1082         snprintf(curlVersion, sizeof(curlVersion), "%u.%u.%u",
1083                 (curlInfoP->version_num >> 16) && 0xff,
1084                 (curlInfoP->version_num >>  8) && 0xff,
1085                 (curlInfoP->version_num >>  0) && 0xff
1086             );
1087                   
1088         xmlrpc_asprintf(&userAgentHeader,
1089                         "User-Agent: %s Xmlrpc-c/%s Curl/%s",
1090                         userAgent, XMLRPC_C_VERSION, curlVersion);
1091         
1092         if (userAgentHeader == xmlrpc_strsol)
1093             xmlrpc_faultf(envP, "Couldn't allocate memory for "
1094                           "User-Agent header");
1095         else {
1096             addHeader(envP, headerListP, userAgentHeader);
1097             
1098             xmlrpc_strfree(userAgentHeader);
1099         }
1100     }
1101 }
1102
1103
1104
1105 static void
1106 addAuthorizationHeader(xmlrpc_env *         const envP,
1107                        struct curl_slist ** const headerListP,
1108                        const char *         const basicAuthInfo) {
1109
1110     if (basicAuthInfo) {
1111         const char * authorizationHeader;
1112             
1113         xmlrpc_asprintf(&authorizationHeader, "Authorization: %s",
1114                         basicAuthInfo);
1115             
1116         if (authorizationHeader == xmlrpc_strsol)
1117             xmlrpc_faultf(envP, "Couldn't allocate memory for "
1118                           "Authorization header");
1119         else {
1120             addHeader(envP, headerListP, authorizationHeader);
1121
1122             xmlrpc_strfree(authorizationHeader);
1123         }
1124     }
1125 }
1126
1127
1128
1129 static void
1130 createCurlHeaderList(xmlrpc_env *               const envP,
1131                      const xmlrpc_server_info * const serverP,
1132                      const char *               const userAgent,
1133                      struct curl_slist **       const headerListP) {
1134
1135     struct curl_slist * headerList;
1136
1137     headerList = NULL;  /* initial value - empty list */
1138
1139     addContentTypeHeader(envP, &headerList);
1140     if (!envP->fault_occurred) {
1141         addUserAgentHeader(envP, &headerList, userAgent);
1142         if (!envP->fault_occurred) {
1143             addAuthorizationHeader(envP, &headerList, 
1144                                    serverP->_http_basic_auth);
1145         }
1146     }
1147     if (envP->fault_occurred)
1148         curl_slist_free_all(headerList);
1149     else
1150         *headerListP = headerList;
1151 }
1152
1153
1154
1155 static void
1156 setupCurlSession(xmlrpc_env *             const envP,
1157                  curlTransaction *        const curlTransactionP,
1158                  xmlrpc_mem_block *       const callXmlP,
1159                  xmlrpc_mem_block *       const responseXmlP,
1160                  const struct curlSetup * const curlSetupP) {
1161 /*----------------------------------------------------------------------------
1162    Set up the Curl session for the transaction *curlTransactionP so that
1163    a subsequent curl_easy_perform() will perform said transaction.
1164 -----------------------------------------------------------------------------*/
1165     CURL * const curlSessionP = curlTransactionP->curlSessionP;
1166
1167     assertConstantsMatch();
1168
1169     curl_easy_setopt(curlSessionP, CURLOPT_POST, 1);
1170     curl_easy_setopt(curlSessionP, CURLOPT_URL, curlTransactionP->serverUrl);
1171
1172     XMLRPC_MEMBLOCK_APPEND(char, envP, callXmlP, "\0", 1);
1173     if (!envP->fault_occurred) {
1174         curl_easy_setopt(curlSessionP, CURLOPT_POSTFIELDS, 
1175                          XMLRPC_MEMBLOCK_CONTENTS(char, callXmlP));
1176         
1177         curl_easy_setopt(curlSessionP, CURLOPT_WRITEFUNCTION, collect);
1178         curl_easy_setopt(curlSessionP, CURLOPT_FILE, responseXmlP);
1179         curl_easy_setopt(curlSessionP, CURLOPT_HEADER, 0);
1180         curl_easy_setopt(curlSessionP, CURLOPT_ERRORBUFFER, 
1181                          curlTransactionP->curlError);
1182         curl_easy_setopt(curlSessionP, CURLOPT_NOPROGRESS, 1);
1183         
1184         curl_easy_setopt(curlSessionP, CURLOPT_HTTPHEADER, 
1185                          curlTransactionP->headerList);
1186
1187         curl_easy_setopt(curlSessionP, CURLOPT_SSL_VERIFYPEER,
1188                          curlSetupP->sslVerifyPeer);
1189         curl_easy_setopt(curlSessionP, CURLOPT_SSL_VERIFYHOST,
1190                          curlSetupP->sslVerifyHost ? 2 : 0);
1191
1192         if (curlSetupP->networkInterface)
1193             curl_easy_setopt(curlSessionP, CURLOPT_INTERFACE,
1194                              curlSetupP->networkInterface);
1195         if (curlSetupP->sslCert)
1196             curl_easy_setopt(curlSessionP, CURLOPT_SSLCERT,
1197                              curlSetupP->sslCert);
1198         if (curlSetupP->sslCertType)
1199             curl_easy_setopt(curlSessionP, CURLOPT_SSLCERTTYPE,
1200                              curlSetupP->sslCertType);
1201         if (curlSetupP->sslCertPasswd)
1202             curl_easy_setopt(curlSessionP, CURLOPT_SSLCERTPASSWD,
1203                              curlSetupP->sslCertPasswd);
1204         if (curlSetupP->sslKey)
1205             curl_easy_setopt(curlSessionP, CURLOPT_SSLKEY,
1206                              curlSetupP->sslKey);
1207         if (curlSetupP->sslKeyType)
1208             curl_easy_setopt(curlSessionP, CURLOPT_SSLKEYTYPE,
1209                              curlSetupP->sslKeyType);
1210         if (curlSetupP->sslKeyPasswd)
1211             curl_easy_setopt(curlSessionP, CURLOPT_SSLKEYPASSWD,
1212                              curlSetupP->sslKeyPasswd);
1213         if (curlSetupP->sslEngine)
1214             curl_easy_setopt(curlSessionP, CURLOPT_SSLENGINE,
1215                              curlSetupP->sslEngine);
1216         if (curlSetupP->sslEngineDefault)
1217             /* 3rd argument seems to be required by some Curl */
1218             curl_easy_setopt(curlSessionP, CURLOPT_SSLENGINE_DEFAULT, 1l);
1219         if (curlSetupP->sslVersion != XMLRPC_SSLVERSION_DEFAULT)
1220             curl_easy_setopt(curlSessionP, CURLOPT_SSLVERSION,
1221                              curlSetupP->sslVersion);
1222         if (curlSetupP->caInfo)
1223             curl_easy_setopt(curlSessionP, CURLOPT_CAINFO,
1224                              curlSetupP->caInfo);
1225         if (curlSetupP->caPath)
1226             curl_easy_setopt(curlSessionP, CURLOPT_CAPATH,
1227                              curlSetupP->caPath);
1228         if (curlSetupP->randomFile)
1229             curl_easy_setopt(curlSessionP, CURLOPT_RANDOM_FILE,
1230                              curlSetupP->randomFile);
1231         if (curlSetupP->egdSocket)
1232             curl_easy_setopt(curlSessionP, CURLOPT_EGDSOCKET,
1233                              curlSetupP->egdSocket);
1234         if (curlSetupP->sslCipherList)
1235             curl_easy_setopt(curlSessionP, CURLOPT_SSL_CIPHER_LIST,
1236                              curlSetupP->sslCipherList);
1237     }
1238 }
1239
1240
1241
1242 static void
1243 createCurlTransaction(xmlrpc_env *               const envP,
1244                       CURL *                     const curlSessionP,
1245                       struct curlMulti *         const curlMultiP,
1246                       const xmlrpc_server_info * const serverP,
1247                       xmlrpc_mem_block *         const callXmlP,
1248                       xmlrpc_mem_block *         const responseXmlP,
1249                       const char *               const userAgent,
1250                       const struct curlSetup *   const curlSetupStuffP,
1251                       rpc *                      const rpcP,
1252                       curlTransaction **         const curlTransactionPP) {
1253
1254     curlTransaction * curlTransactionP;
1255
1256     MALLOCVAR(curlTransactionP);
1257     if (curlTransactionP == NULL)
1258         xmlrpc_faultf(envP, "No memory to create Curl transaction.");
1259     else {
1260         curlTransactionP->curlSessionP = curlSessionP;
1261         curlTransactionP->curlMultiP   = curlMultiP;
1262         curlTransactionP->rpcP         = rpcP;
1263
1264         curlTransactionP->serverUrl = strdup(serverP->_server_url);
1265         if (curlTransactionP->serverUrl == NULL)
1266             xmlrpc_faultf(envP, "Out of memory to store server URL.");
1267         else {
1268             createCurlHeaderList(envP, serverP, userAgent,
1269                                  &curlTransactionP->headerList);
1270             
1271             if (!envP->fault_occurred)
1272                 setupCurlSession(envP, curlTransactionP,
1273                                  callXmlP, responseXmlP,
1274                                  curlSetupStuffP);
1275
1276             if (envP->fault_occurred)
1277                 xmlrpc_strfree(curlTransactionP->serverUrl);
1278         }
1279         if (envP->fault_occurred)
1280             free(curlTransactionP);
1281     }
1282     *curlTransactionPP = curlTransactionP;
1283 }
1284
1285
1286
1287 static void
1288 destroyCurlTransaction(curlTransaction * const curlTransactionP) {
1289
1290     curl_slist_free_all(curlTransactionP->headerList);
1291     xmlrpc_strfree(curlTransactionP->serverUrl);
1292
1293     free(curlTransactionP);
1294 }
1295
1296
1297
1298 static void
1299 getCurlTransactionError(curlTransaction * const curlTransactionP,
1300                         xmlrpc_env *      const envP) {
1301
1302     CURLcode res;
1303     long http_result;
1304
1305     res = curl_easy_getinfo(curlTransactionP->curlSessionP,
1306                             CURLINFO_HTTP_CODE, &http_result);
1307     
1308     if (res != CURLE_OK)
1309         xmlrpc_env_set_fault_formatted(
1310             envP, XMLRPC_INTERNAL_ERROR, 
1311             "Curl performed the HTTP POST request, but was "
1312             "unable to say what the HTTP result code was.  "
1313             "curl_easy_getinfo(CURLINFO_HTTP_CODE) says: %s", 
1314             curlTransactionP->curlError);
1315     else {
1316         if (http_result != 200)
1317             xmlrpc_env_set_fault_formatted(
1318                 envP, XMLRPC_NETWORK_ERROR, "HTTP response: %ld",
1319                 http_result);
1320     }
1321 }
1322
1323
1324
1325 static void
1326 performCurlTransaction(xmlrpc_env *      const envP,
1327                        curlTransaction * const curlTransactionP) {
1328
1329     CURL * const curlSessionP = curlTransactionP->curlSessionP;
1330
1331     CURLcode res;
1332
1333     res = curl_easy_perform(curlSessionP);
1334     
1335     if (res != CURLE_OK)
1336         xmlrpc_env_set_fault_formatted(
1337             envP, XMLRPC_NETWORK_ERROR, "Curl failed to perform "
1338             "HTTP POST request.  curl_easy_perform() says: %s", 
1339             curlTransactionP->curlError);
1340     else
1341         getCurlTransactionError(curlTransactionP, envP);
1342 }
1343
1344
1345
1346 static void
1347 createRpc(xmlrpc_env *                     const envP,
1348           struct xmlrpc_client_transport * const clientTransportP,
1349           CURL *                           const curlSessionP,
1350           const xmlrpc_server_info *       const serverP,
1351           xmlrpc_mem_block *               const callXmlP,
1352           xmlrpc_mem_block *               const responseXmlP,
1353           xmlrpc_transport_asynch_complete       complete, 
1354           struct xmlrpc_call_info *        const callInfoP,
1355           rpc **                           const rpcPP) {
1356
1357     rpc * rpcP;
1358
1359     MALLOCVAR(rpcP);
1360     if (rpcP == NULL)
1361         xmlrpc_faultf(envP, "Couldn't allocate memory for rpc object");
1362     else {
1363         rpcP->callInfoP    = callInfoP;
1364         rpcP->complete     = complete;
1365         rpcP->responseXmlP = responseXmlP;
1366
1367         createCurlTransaction(envP,
1368                               curlSessionP,
1369                               clientTransportP->curlMultiP,
1370                               serverP,
1371                               callXmlP, responseXmlP, 
1372                               clientTransportP->userAgent,
1373                               &clientTransportP->curlSetupStuff,
1374                               rpcP,
1375                               &rpcP->curlTransactionP);
1376         if (!envP->fault_occurred) {
1377             if (envP->fault_occurred)
1378                 destroyCurlTransaction(rpcP->curlTransactionP);
1379         }
1380         if (envP->fault_occurred)
1381             free(rpcP);
1382     }
1383     *rpcPP = rpcP;
1384 }
1385
1386
1387
1388 static void 
1389 destroyRpc(rpc * const rpcP) {
1390
1391     XMLRPC_ASSERT_PTR_OK(rpcP);
1392
1393     destroyCurlTransaction(rpcP->curlTransactionP);
1394
1395     free(rpcP);
1396 }
1397
1398
1399
1400 static void
1401 performRpc(xmlrpc_env * const envP,
1402            rpc *        const rpcP) {
1403
1404     performCurlTransaction(envP, rpcP->curlTransactionP);
1405 }
1406
1407
1408
1409 static void
1410 startCurlTransaction(xmlrpc_env *      const envP,
1411                      curlTransaction * const curlTransactionP) {
1412
1413     /* A Curl session is serial -- it processes zero or one transaction
1414        at a time.  We use the "private" attribute of the Curl session to
1415        indicate which transaction it is presently processing.  This is
1416        important when the transaction finishes, because libcurl will just
1417        tell us that something finished on a particular session, not that
1418        a particular transaction finished.
1419     */
1420     curl_easy_setopt(curlTransactionP->curlSessionP, CURLOPT_PRIVATE,
1421                      curlTransactionP);
1422
1423     curlMulti_addHandle(envP,
1424                         curlTransactionP->curlMultiP,
1425                         curlTransactionP->curlSessionP);
1426 }
1427
1428
1429
1430 static void
1431 startRpc(xmlrpc_env * const envP,
1432          rpc *        const rpcP) {
1433
1434     startCurlTransaction(envP, rpcP->curlTransactionP);
1435 }
1436
1437
1438
1439 static void 
1440 sendRequest(xmlrpc_env *                     const envP, 
1441             struct xmlrpc_client_transport * const clientTransportP,
1442             const xmlrpc_server_info *       const serverP,
1443             xmlrpc_mem_block *               const callXmlP,
1444             xmlrpc_transport_asynch_complete       complete,
1445             struct xmlrpc_call_info *        const callInfoP) {
1446 /*----------------------------------------------------------------------------
1447    Initiate an XML-RPC rpc asynchronously.  Don't wait for it to go to
1448    the server.
1449
1450    Unless we return failure, we arrange to have complete() called when
1451    the rpc completes.
1452
1453    This does the 'send_request' operation for a Curl client transport.
1454 -----------------------------------------------------------------------------*/
1455     rpc * rpcP;
1456     xmlrpc_mem_block * responseXmlP;
1457
1458     responseXmlP = XMLRPC_MEMBLOCK_NEW(char, envP, 0);
1459     if (!envP->fault_occurred) {
1460         CURL * const curlSessionP = curl_easy_init();
1461     
1462         if (curlSessionP == NULL)
1463             xmlrpc_faultf(envP, "Could not create Curl session.  "
1464                           "curl_easy_init() failed.");
1465         else {
1466             createRpc(envP, clientTransportP, curlSessionP, serverP,
1467                       callXmlP, responseXmlP, complete, callInfoP,
1468                       &rpcP);
1469             
1470             if (!envP->fault_occurred) {
1471                 startRpc(envP, rpcP);
1472                 
1473                 if (envP->fault_occurred)
1474                     destroyRpc(rpcP);
1475             }
1476             if (envP->fault_occurred)
1477                 curl_easy_cleanup(curlSessionP);
1478         }
1479         if (envP->fault_occurred)
1480             XMLRPC_MEMBLOCK_FREE(char, responseXmlP);
1481     }
1482     /* If we're returning success, the user's eventual finish_asynch
1483        call will destroy this RPC, Curl session, and response buffer
1484        and remove the Curl session from the Curl multi manager.
1485        (If we're returning failure, we didn't create any of those).
1486     */
1487 }
1488
1489
1490
1491 static void
1492 finishCurlTransaction(xmlrpc_env * const envP ATTR_UNUSED,
1493                       CURL *       const curlSessionP,
1494                       CURLcode     const result) {
1495 /*----------------------------------------------------------------------------
1496   Handle the event that a Curl transaction has completed on the Curl
1497   session identified by 'curlSessionP'.
1498
1499   Tell the requester of the RPC which this transaction serves the
1500   results.
1501
1502   Remove the Curl session from its Curl multi manager and destroy the
1503   Curl session, the XML response buffer, the Curl transaction, and the RPC.
1504 -----------------------------------------------------------------------------*/
1505     curlTransaction * curlTransactionP;
1506     rpc * rpcP;
1507
1508     curl_easy_getinfo(curlSessionP, CURLINFO_PRIVATE, &curlTransactionP);
1509
1510     rpcP = curlTransactionP->rpcP;
1511
1512     curlMulti_removeHandle(curlTransactionP->curlMultiP,
1513                            curlTransactionP->curlSessionP);
1514     {
1515         xmlrpc_env env;
1516
1517         xmlrpc_env_init(&env);
1518
1519         if (result != CURLE_OK) {
1520             xmlrpc_env_set_fault_formatted(
1521                 envP, XMLRPC_NETWORK_ERROR, "libcurl failed to execute the "
1522                 "HTTP POST transaction.  %s", curlTransactionP->curlError);
1523         } else
1524             getCurlTransactionError(curlTransactionP, &env);
1525
1526         rpcP->complete(rpcP->callInfoP, rpcP->responseXmlP, env);
1527
1528         xmlrpc_env_clean(&env);
1529     }
1530
1531     curl_easy_cleanup(curlSessionP);
1532
1533     XMLRPC_MEMBLOCK_FREE(char, rpcP->responseXmlP);
1534
1535     destroyRpc(rpcP);
1536 }
1537
1538
1539
1540 static struct timeval
1541 selectTimeout(xmlrpc_timeoutType const timeoutType,
1542               struct timeval     const timeoutTime) {
1543 /*----------------------------------------------------------------------------
1544    Return the value that should be used in the select() call to wait for
1545    there to be work for the Curl multi manager to do, given that the user
1546    wants to timeout according to 'timeoutType' and 'timeoutTime'.
1547 -----------------------------------------------------------------------------*/
1548     unsigned int selectTimeoutMillisec;
1549     struct timeval retval;
1550
1551     selectTimeoutMillisec = 0; // quiet compiler warning
1552
1553     /* We assume there is work to do at least every 3 seconds, because
1554        the Curl multi manager often has retries and other scheduled work
1555        that doesn't involve file handles on which we can select().
1556     */
1557     switch (timeoutType) {
1558     case timeout_no:
1559         selectTimeoutMillisec = 3000;
1560         break;
1561     case timeout_yes: {
1562         struct timeval nowTime;
1563         int timeLeft;
1564
1565         gettimeofday(&nowTime, NULL);
1566         timeLeft = timeDiffMillisec(timeoutTime, nowTime);
1567
1568         selectTimeoutMillisec = MIN(3000, MAX(0, timeLeft));
1569     }
1570     break;
1571     }
1572     retval.tv_sec = selectTimeoutMillisec / 1000;
1573     retval.tv_usec = (selectTimeoutMillisec % 1000) * 1000;
1574
1575     return retval;
1576 }        
1577
1578
1579
1580 static void
1581 processCurlMessages(xmlrpc_env *       const envP,
1582                     struct curlMulti * const curlMultiP) {
1583         
1584     bool endOfMessages;
1585
1586     endOfMessages = false;   /* initial assumption */
1587
1588     while (!endOfMessages && !envP->fault_occurred) {
1589         CURLMsg curlMsg;
1590
1591         curlMulti_getMessage(curlMultiP, &endOfMessages, &curlMsg);
1592
1593         if (!endOfMessages) {
1594             if (curlMsg.msg == CURLMSG_DONE)
1595                 finishCurlTransaction(envP, curlMsg.easy_handle,
1596                                       curlMsg.data.result);
1597         }
1598     }
1599 }
1600
1601
1602
1603 static void
1604 waitForWork(xmlrpc_env *       const envP,
1605             struct curlMulti * const curlMultiP,
1606             xmlrpc_timeoutType const timeoutType,
1607             struct timeval     const deadline) {
1608     
1609     fd_set readFdSet;
1610     fd_set writeFdSet;
1611     fd_set exceptFdSet;
1612     int maxFd;
1613
1614     curlMulti_fdset(envP, curlMultiP,
1615                     &readFdSet, &writeFdSet, &exceptFdSet, &maxFd);
1616     if (!envP->fault_occurred) {
1617         if (maxFd == -1) {
1618             /* There are no Curl file descriptors on which to wait.
1619                So either there's work to do right now or all transactions
1620                are already complete.
1621             */
1622         } else {
1623             struct timeval selectTimeoutArg;
1624             int rc;
1625             
1626             selectTimeoutArg = selectTimeout(timeoutType, deadline);
1627
1628             rc = select(maxFd+1, &readFdSet, &writeFdSet, &exceptFdSet,
1629                         &selectTimeoutArg);
1630             
1631             if (rc < 0)
1632                 xmlrpc_faultf(envP, "Impossible failure of select() "
1633                               "with errno %d (%s)",
1634                               errno, strerror(errno));
1635             else {
1636                 /* Believe it or not, the Curl multi manager needs the
1637                    results of our select().  So hand them over:
1638                 */
1639                 curlMulti_updateFdSet(curlMultiP,
1640                                       readFdSet, writeFdSet, exceptFdSet);
1641             }
1642         }
1643     }
1644 }
1645
1646
1647
1648 static void
1649 doCurlWork(xmlrpc_env *       const envP,
1650            struct curlMulti * const curlMultiP,
1651            bool *             const rpcStillRunningP) {
1652 /*----------------------------------------------------------------------------
1653    Do whatever work is ready to be done by the Curl multi manager
1654    identified by 'curlMultiP'.  This typically is transferring data on
1655    an HTTP connection because the server is ready.
1656
1657    Return *rpcStillRunningP false if this work completes all of the
1658    manager's transactions so that there is no reason to call us ever
1659    again.
1660
1661    Where the multi manager completes an HTTP transaction, also complete
1662    the associated RPC.
1663 -----------------------------------------------------------------------------*/
1664     bool immediateWorkToDo;
1665     int runningHandles;
1666
1667     immediateWorkToDo = true;  /* initial assumption */
1668
1669     while (immediateWorkToDo && !envP->fault_occurred) {
1670         curlMulti_perform(envP, curlMultiP,
1671                           &immediateWorkToDo, &runningHandles);
1672     }
1673
1674     /* We either did all the work that's ready to do or hit an error. */
1675
1676     if (!envP->fault_occurred) {
1677         /* The work we did may have resulted in asynchronous messages
1678            (asynchronous to the thing the refer to, not to us, of course).
1679            In particular the message "Curl transaction has completed".
1680            So we process those now.
1681         */
1682         processCurlMessages(envP, curlMultiP);
1683
1684         *rpcStillRunningP = runningHandles > 0;
1685     }
1686 }
1687
1688
1689
1690 static void
1691 finishCurlSessions(xmlrpc_env *       const envP,
1692                    struct curlMulti * const curlMultiP,
1693                    xmlrpc_timeoutType const timeoutType,
1694                    struct timeval     const deadline) {
1695
1696     bool rpcStillRunning;
1697     bool timedOut;
1698     
1699     rpcStillRunning = true;  /* initial assumption */
1700     timedOut = false;
1701     
1702     while (rpcStillRunning && !timedOut && !envP->fault_occurred) {
1703         waitForWork(envP, curlMultiP, timeoutType, deadline);
1704
1705         if (!envP->fault_occurred) {
1706             struct timeval nowTime;
1707
1708             doCurlWork(envP, curlMultiP, &rpcStillRunning);
1709             
1710             gettimeofday(&nowTime, NULL);
1711             
1712             timedOut = (timeoutType == timeout_yes &&
1713                         timeIsAfter(nowTime, deadline));
1714         }
1715     }
1716 }
1717
1718
1719
1720 static void 
1721 finishAsynch(
1722     struct xmlrpc_client_transport * const clientTransportP,
1723     xmlrpc_timeoutType               const timeoutType,
1724     xmlrpc_timeout                   const timeout) {
1725 /*----------------------------------------------------------------------------
1726    Wait for the Curl multi manager to finish the Curl transactions for
1727    all outstanding RPCs and destroy those RPCs.
1728
1729    This does the 'finish_asynch' operation for a Curl client transport.
1730
1731    It would be cool to replace this with something analogous to the
1732    Curl asynchronous interface: Have something like curl_multi_fdset()
1733    that returns a bunch of file descriptors on which the user can wait
1734    (along with possibly other file descriptors of his own) and
1735    something like curl_multi_perform() to finish whatever RPCs are
1736    ready to finish at that moment.  The implementation would be little
1737    more than wrapping curl_multi_fdset() and curl_multi_perform().
1738 -----------------------------------------------------------------------------*/
1739     xmlrpc_env env;
1740
1741     struct timeval waitTimeoutTime;
1742         /* The datetime after which we should quit waiting */
1743
1744     xmlrpc_env_init(&env);
1745     
1746     if (timeoutType == timeout_yes) {
1747         struct timeval waitStartTime;
1748         gettimeofday(&waitStartTime, NULL);
1749         addMilliseconds(waitStartTime, timeout, &waitTimeoutTime);
1750     }
1751
1752     finishCurlSessions(&env, clientTransportP->curlMultiP,
1753                        timeoutType, waitTimeoutTime);
1754
1755     /* If the above fails, it is catastrophic, because it means there is
1756        no way to complete outstanding Curl transactions and RPCs, and
1757        no way to release their resources.
1758
1759        We should at least expand this interface some day to push the
1760        problem back up the user, but for now we just do this Hail Mary
1761        response.
1762
1763        Note that a failure of finishCurlSessions() does not mean that
1764        a session completed with an error or an RPC completed with an
1765        error.  Those things are reported up through the user's 
1766        xmlrpc_transport_asynch_complete routine.  A failure here is
1767        something that stopped us from calling that.
1768     */
1769
1770     if (env.fault_occurred)
1771         fprintf(stderr, "finishAsync() failed.  Xmlrpc-c Curl transport "
1772                 "is now in an unknown state and may not be able to "
1773                 "continue functioning.  Specifics of the failure: %s\n",
1774                 env.fault_string);
1775
1776     xmlrpc_env_clean(&env);
1777 }
1778
1779
1780
1781 static void
1782 call(xmlrpc_env *                     const envP,
1783      struct xmlrpc_client_transport * const clientTransportP,
1784      const xmlrpc_server_info *       const serverP,
1785      xmlrpc_mem_block *               const callXmlP,
1786      xmlrpc_mem_block **              const responseXmlPP) {
1787
1788     xmlrpc_mem_block * responseXmlP;
1789     rpc * rpcP;
1790
1791     XMLRPC_ASSERT_ENV_OK(envP);
1792     XMLRPC_ASSERT_PTR_OK(serverP);
1793     XMLRPC_ASSERT_PTR_OK(callXmlP);
1794     XMLRPC_ASSERT_PTR_OK(responseXmlPP);
1795
1796     responseXmlP = XMLRPC_MEMBLOCK_NEW(char, envP, 0);
1797     if (!envP->fault_occurred) {
1798         /* Only one RPC at a time can use a Curl session, so we have to
1799            hold the lock as long as our RPC exists.
1800         */
1801         lockSyncCurlSession(clientTransportP);
1802         createRpc(envP, clientTransportP, clientTransportP->syncCurlSessionP,
1803                   serverP,
1804                   callXmlP, responseXmlP,
1805                   NULL, NULL,
1806                   &rpcP);
1807
1808         if (!envP->fault_occurred) {
1809             performRpc(envP, rpcP);
1810
1811             *responseXmlPP = responseXmlP;
1812
1813             destroyRpc(rpcP);
1814         }
1815         unlockSyncCurlSession(clientTransportP);
1816         if (envP->fault_occurred)
1817             XMLRPC_MEMBLOCK_FREE(char, responseXmlP);
1818     }
1819 }
1820
1821
1822
1823 static void
1824 setupGlobalConstants(xmlrpc_env * const envP) {
1825 /*----------------------------------------------------------------------------
1826    See longwinded discussion of the global constant issue at the top of
1827    this file.
1828 -----------------------------------------------------------------------------*/
1829     initWindowsStuff(envP);
1830
1831     if (!envP->fault_occurred) {
1832         CURLcode rc;
1833
1834         rc = curl_global_init(CURL_GLOBAL_ALL);
1835         
1836         if (rc != CURLE_OK)
1837             xmlrpc_faultf(envP, "curl_global_init() failed with code %d", rc);
1838     }
1839 }
1840
1841
1842
1843 static void
1844 teardownGlobalConstants(void) {
1845 /*----------------------------------------------------------------------------
1846    See longwinded discussionof the global constant issue at the top of
1847    this file.
1848 -----------------------------------------------------------------------------*/
1849     curl_global_cleanup();
1850
1851     termWindowsStuff();
1852 }
1853
1854
1855
1856 struct xmlrpc_client_transport_ops xmlrpc_curl_transport_ops = {
1857     &setupGlobalConstants,
1858     &teardownGlobalConstants,
1859     &create,
1860     &destroy,
1861     &sendRequest,
1862     &call,
1863     &finishAsynch,
1864 };