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 #define _XOPEN_SOURCE 600  /* Make sure strdup() is in <string.h> */
56 
57 #include "xmlrpc_config.h"
58 
59 #include <string.h>
60 #include <stdlib.h>
61 #include <errno.h>
62 #include <assert.h>
63 #include <limits.h>
64 #if HAVE_SYS_SELECT_H
65 #include <sys/select.h>
66 #endif
67 #include <signal.h>
68 
69 #ifdef WIN32
70 #include "curllink.h"
71 #endif
72 
73 #include "bool.h"
74 #include "girmath.h"
75 #include "mallocvar.h"
76 #include "linklist.h"
77 #include "girstring.h"
78 #include "pthreadx.h"
79 
80 #include "xmlrpc-c/util.h"
81 #include "xmlrpc-c/string_int.h"
82 #include "xmlrpc-c/select_int.h"
83 #include "xmlrpc-c/client_int.h"
84 #include "xmlrpc-c/transport.h"
85 #include "xmlrpc-c/time_int.h"
86 
87 #include <curl/curl.h>
88 #include <curl/types.h>
89 #include <curl/easy.h>
90 #include <curl/multi.h>
91 
92 #include "lock.h"
93 #include "lock_pthread.h"
94 #include "curltransaction.h"
95 #include "curlmulti.h"
96 #include "curlversion.h"
97 
98 #if MSVCRT
99 #if defined(_DEBUG)
100 #  include <crtdbg.h>
101 #  define new DEBUG_NEW
102 #  define malloc(size) _malloc_dbg( size, _NORMAL_BLOCK, __FILE__, __LINE__)
103 #  undef THIS_FILE
104    static char THIS_FILE[] = __FILE__;
105 #endif
106 #endif
107 
108 
109 typedef struct rpc rpc;
110 
111 
112 
113 static int
timeDiffMillisec(xmlrpc_timespec const minuend,xmlrpc_timespec const subtractor)114 timeDiffMillisec(xmlrpc_timespec const minuend,
115                  xmlrpc_timespec const subtractor) {
116 
117     unsigned int const million = 1000000;
118 
119     return (minuend.tv_sec - subtractor.tv_sec) * 1000 +
120         (minuend.tv_nsec - subtractor.tv_nsec + million/2) / million;
121 }
122 
123 
124 
125 static bool
timeIsAfter(xmlrpc_timespec const comparator,xmlrpc_timespec const comparand)126 timeIsAfter(xmlrpc_timespec const comparator,
127             xmlrpc_timespec const comparand) {
128 
129     if (comparator.tv_sec > comparand.tv_sec)
130         return true;
131     else if (comparator.tv_sec < comparand.tv_sec)
132         return false;
133     else {
134         /* Seconds are equal */
135         if (comparator.tv_nsec > comparand.tv_nsec)
136             return true;
137         else
138             return false;
139     }
140 }
141 
142 
143 
144 static void
addMilliseconds(xmlrpc_timespec const addend,unsigned int const adder,xmlrpc_timespec * const sumP)145 addMilliseconds(xmlrpc_timespec   const addend,
146                 unsigned int      const adder,
147                 xmlrpc_timespec * const sumP) {
148 
149     unsigned int const million = 1000000;
150     unsigned int const billion = 1000000000;
151 
152     xmlrpc_timespec sum;
153 
154     sum.tv_sec  = addend.tv_sec + adder / 1000;
155     sum.tv_nsec = addend.tv_nsec + (adder % 1000) * million;
156 
157     if ((uint32_t)sum.tv_nsec >= billion) {
158         sum.tv_sec += 1;
159         sum.tv_nsec -= billion;
160     }
161     *sumP = sum;
162 }
163 
164 
165 
166 struct xmlrpc_client_transport {
167     CURL * syncCurlSessionP;
168         /* Handle for a Curl library session object that we use for
169            all synchronous RPCs.  An async RPC has one of its own,
170            and consequently does not share things such as persistent
171            connections and cookies with any other RPC.
172         */
173     lock * syncCurlSessionLockP;
174         /* Hold this lock while accessing or using *syncCurlSessionP.
175            You're using the session from the time you set any
176            attributes in it or start a transaction with it until any
177            transaction has finished and you've lost interest in any
178            attributes of the session.
179         */
180     curlMulti * syncCurlMultiP;
181         /* The Curl multi manager that this transport uses to execute
182            Curl transactions for RPCs requested via the synchronous
183            interface.  The fact that there is never more than one such
184            transaction going at a time might make you wonder why a
185            "multi" manager is needed.  The reason is that it is the only
186            interface in libcurl that gives us the flexibility to execute
187            the transaction with proper interruptibility.  The only Curl
188            transaction ever attached to this multi manager is
189            'syncCurlSessionP'.
190 
191            This is constant (the handle, not the object).
192         */
193     curlMulti * asyncCurlMultiP;
194         /* The Curl multi manager that this transport uses to execute
195            Curl transactions for RPCs requested via the asynchronous
196            interface.  Note that there may be multiple such Curl transactions
197            simultaneously and one can't wait for a particular one to finish;
198            the collection of asynchronous RPCs are an indivisible mass.
199 
200            This is constant (the handle, not the object).
201         */
202     bool dontAdvertise;
203         /* Don't identify to the server the XML-RPC engine we are using.  If
204            false, include a User-Agent HTTP header in all requests that
205            identifies the Xmlrpc-c and Curl libraries.
206 
207            See also 'userAgent'.
208 
209            This is constant.
210         */
211     const char * userAgent;
212         /* Information to include in a User-Agent HTTP header, reflecting
213            facilities outside of Xmlrpc-c.
214 
215            Null means none.
216 
217            The full User-Agent header value is this information (if
218            'userAgent' is non-null) followed by identification of Xmlrpc-c
219            and Curl (if 'dontAdvertise' is false).  If 'userAgent' is null
220            and 'dontAdvertise' is true, we put no User-Agent header at all
221            in the request.
222 
223            This is constant.
224         */
225     struct curlSetup curlSetupStuff;
226         /* This is constant */
227     int * interruptP;
228         /* Pointer to a value that user sets to nonzero to indicate he wants
229            the transport to give up on whatever it is doing and return ASAP.
230 
231            NULL means none -- transport never gives up.
232 
233            This is constant.
234         */
235 };
236 
237 
238 
239 struct rpc {
240     struct xmlrpc_client_transport * transportP;
241         /* The client XML transport that transports this RPC */
242     curlTransaction * curlTransactionP;
243         /* The object which does the HTTP transaction, with no knowledge
244            of XML-RPC or Xmlrpc-c.
245         */
246     CURL * curlSessionP;
247         /* The Curl session to use for the Curl transaction to perform
248            the RPC.
249         */
250     xmlrpc_mem_block * responseXmlP;
251         /* Where the response XML for this RPC should go or has gone. */
252     xmlrpc_transport_asynch_complete complete;
253         /* Routine to call to complete the RPC after it is complete HTTP-wise.
254            NULL if none.
255         */
256     xmlrpc_transport_progress progress;
257         /* Routine to call periodically to report the progress of transporting
258            the call and response.  NULL if none.
259         */
260     struct xmlrpc_call_info * callInfoP;
261         /* User's identifier for this RPC */
262 };
263 
264 
265 static void
lockSyncCurlSession(struct xmlrpc_client_transport * const transportP)266 lockSyncCurlSession(struct xmlrpc_client_transport * const transportP) {
267     transportP->syncCurlSessionLockP->acquire(
268         transportP->syncCurlSessionLockP);
269 }
270 
271 
272 
273 static void
unlockSyncCurlSession(struct xmlrpc_client_transport * const transportP)274 unlockSyncCurlSession(struct xmlrpc_client_transport * const transportP) {
275     transportP->syncCurlSessionLockP->release(
276         transportP->syncCurlSessionLockP);
277 }
278 
279 
280 
281 static void
initWindowsStuff(xmlrpc_env * const envP ATTR_UNUSED)282 initWindowsStuff(xmlrpc_env * const envP ATTR_UNUSED) {
283 
284 #if defined (WIN32)
285     /* This is CRITICAL so that cURL-Win32 works properly! */
286 
287     /* So this commenter says, but I wonder why.  libcurl should do the
288        required WSAStartup() itself, and it looks to me like it does.
289        -Bryan 06.01.01
290     */
291     WORD wVersionRequested;
292     WSADATA wsaData;
293     int err;
294     wVersionRequested = MAKEWORD(1, 1);
295 
296     err = WSAStartup(wVersionRequested, &wsaData);
297     if (err)
298         xmlrpc_env_set_fault_formatted(
299             envP, XMLRPC_INTERNAL_ERROR,
300             "Winsock startup failed.  WSAStartup returned rc %d", err);
301     else {
302         if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
303             /* Tell the user that we couldn't find a useable */
304             /* winsock.dll. */
305             xmlrpc_env_set_fault_formatted(
306                 envP, XMLRPC_INTERNAL_ERROR, "Winsock reported that "
307                 "it does not implement the requested version 1.1.");
308         }
309         if (envP->fault_occurred)
310             WSACleanup();
311     }
312 #endif
313 }
314 
315 
316 
317 static void
termWindowsStuff(void)318 termWindowsStuff(void) {
319 
320 #if defined (WIN32)
321     WSACleanup();
322 #endif
323 }
324 
325 
326 
327 static bool
curlHasNosignal(void)328 curlHasNosignal(void) {
329 
330     bool retval;
331 
332 #if HAVE_CURL_NOSIGNAL
333     curl_version_info_data * const curlInfoP =
334         curl_version_info(CURLVERSION_NOW);
335 
336     retval = (curlInfoP->version_num >= 0x070A00);  /* 7.10.0 */
337 #else
338     retval = false;
339 #endif
340     return retval;
341 }
342 
343 
344 
345 static xmlrpc_timespec
pselectTimeout(xmlrpc_timeoutType const timeoutType,xmlrpc_timespec const timeoutDt)346 pselectTimeout(xmlrpc_timeoutType const timeoutType,
347                xmlrpc_timespec    const timeoutDt) {
348 /*----------------------------------------------------------------------------
349    Return the value that should be used in the select() call to wait for
350    there to be work for the Curl multi manager to do, given that the user
351    wants to timeout according to 'timeoutType' and 'timeoutDt'.
352 -----------------------------------------------------------------------------*/
353     unsigned int const million = 1000000;
354     unsigned int selectTimeoutMillisec;
355     xmlrpc_timespec retval;
356 
357     /* We assume there is work to do at least every 3 seconds, because
358        the Curl multi manager often has retries and other scheduled work
359        that doesn't involve file handles on which we can select().
360     */
361     switch (timeoutType) {
362     case timeout_no:
363         selectTimeoutMillisec = 3000;
364         break;
365     case timeout_yes: {
366         xmlrpc_timespec nowTime;
367         int timeLeft;
368 
369         xmlrpc_gettimeofday(&nowTime);
370         timeLeft = timeDiffMillisec(timeoutDt, nowTime);
371 
372         selectTimeoutMillisec = MIN(3000, MAX(0, timeLeft));
373     } break;
374     }
375     retval.tv_sec = selectTimeoutMillisec / 1000;
376     retval.tv_nsec = (uint32_t)((selectTimeoutMillisec % 1000) * million);
377 
378     return retval;
379 }
380 
381 
382 
383 static void
processCurlMessages(xmlrpc_env * const envP,curlMulti * const curlMultiP)384 processCurlMessages(xmlrpc_env * const envP,
385                     curlMulti *  const curlMultiP) {
386 
387     bool endOfMessages;
388 
389     endOfMessages = false;   /* initial assumption */
390 
391     while (!endOfMessages && !envP->fault_occurred) {
392         CURLMsg curlMsg;
393 
394         curlMulti_getMessage(curlMultiP, &endOfMessages, &curlMsg);
395 
396         if (!endOfMessages) {
397             if (curlMsg.msg == CURLMSG_DONE) {
398                 curlTransaction * curlTransactionP;
399 
400                 curl_easy_getinfo(curlMsg.easy_handle, CURLINFO_PRIVATE,
401                                   (void *)&curlTransactionP);
402 
403                 curlTransaction_finish(envP,
404                                        curlTransactionP, curlMsg.data.result);
405             }
406         }
407     }
408 }
409 
410 
411 
412 static void
waitForWork(xmlrpc_env * const envP,curlMulti * const curlMultiP,xmlrpc_timeoutType const timeoutType,xmlrpc_timespec const deadline,sigset_t * const sigmaskP)413 waitForWork(xmlrpc_env *       const envP,
414             curlMulti *        const curlMultiP,
415             xmlrpc_timeoutType const timeoutType,
416             xmlrpc_timespec    const deadline,
417             sigset_t *         const sigmaskP) {
418 /*----------------------------------------------------------------------------
419    Wait for the Curl multi manager to have work to do, time to run out,
420    or a signal to be received (and caught), whichever comes first.
421 
422    Update the Curl multi manager's file descriptor sets to indicate what
423    work we found for it to do.
424 
425    Wait under signal mask *sigmaskP.  The point of this is that Caller
426    can make sure that arrival of a signal of a certain class
427    interrupts our wait, even if the signal arrives shortly before we
428    begin waiting.  Caller blocks that signal class, then checks
429    whether a signal of that class has already been received.  If not,
430    he calls us with *sigmaskP indicating that class NOT blocked.
431    Thus, if a signal of that class arrived any time after Caller
432    checked, we will return immediately or when the signal arrives,
433    whichever is sooner.  Note that we can provide this service only
434    because pselect() has the same atomic unblock/wait feature.
435 
436    If sigmaskP is NULL, wait under whatever the current signal mask
437    is.
438 -----------------------------------------------------------------------------*/
439     fd_set readFdSet;
440     fd_set writeFdSet;
441     fd_set exceptFdSet;
442     int maxFd;
443 
444     curlMulti_fdset(envP, curlMultiP,
445                     &readFdSet, &writeFdSet, &exceptFdSet, &maxFd);
446     if (!envP->fault_occurred) {
447         if (maxFd == -1) {
448             /* There are no Curl file descriptors on which to wait.
449                So either there's work to do right now or all transactions
450                are already complete.
451             */
452         } else {
453             xmlrpc_timespec const pselectTimeoutArg =
454                 pselectTimeout(timeoutType, deadline);
455 
456             int rc;
457 
458             rc = xmlrpc_pselect(maxFd+1, &readFdSet, &writeFdSet, &exceptFdSet,
459                                 &pselectTimeoutArg, sigmaskP);
460 
461             if (rc < 0 && errno != EINTR)
462                 xmlrpc_faultf(envP, "Impossible failure of pselect() "
463                               "with errno %d (%s)",
464                               errno, strerror(errno));
465             else {
466                 /* Believe it or not, the Curl multi manager needs the
467                    results of our pselect().  So hand them over:
468                 */
469                 curlMulti_updateFdSet(curlMultiP,
470                                       readFdSet, writeFdSet, exceptFdSet);
471             }
472         }
473     }
474 }
475 
476 
477 
478 static void
waitForWorkInt(xmlrpc_env * const envP,curlMulti * const curlMultiP,xmlrpc_timeoutType const timeoutType,xmlrpc_timespec const deadline,int * const interruptP)479 waitForWorkInt(xmlrpc_env *       const envP,
480                curlMulti *        const curlMultiP,
481                xmlrpc_timeoutType const timeoutType,
482                xmlrpc_timespec    const deadline,
483                int *              const interruptP) {
484 /*----------------------------------------------------------------------------
485    Same as waitForWork(), except we guarantee to return if a signal handler
486    sets or has set *interruptP, whereas waitForWork() can miss a signal
487    that happens before or just after it starts.
488 
489    We mess with global state -- the signal mask -- so we might mess up
490    a multithreaded program.  Therefore, don't call this if
491    waitForWork() will suffice.
492 -----------------------------------------------------------------------------*/
493     sigset_t callerBlockSet;
494 #ifdef WIN32
495     waitForWork(envP, curlMultiP, timeoutType, deadline, &callerBlockSet);
496 #else
497     sigset_t allSignals;
498 
499     assert(interruptP != NULL);
500 
501     sigfillset(&allSignals);
502 
503     sigprocmask(SIG_BLOCK, &allSignals, &callerBlockSet);
504 
505     if (*interruptP == 0)
506         waitForWork(envP, curlMultiP, timeoutType, deadline, &callerBlockSet);
507 
508     sigprocmask(SIG_SETMASK, &callerBlockSet, NULL);
509 #endif
510 }
511 
512 
513 
514 static void
doCurlWork(xmlrpc_env * const envP,curlMulti * const curlMultiP,bool * const transStillRunningP)515 doCurlWork(xmlrpc_env * const envP,
516            curlMulti *  const curlMultiP,
517            bool *       const transStillRunningP) {
518 /*----------------------------------------------------------------------------
519    Do whatever work is ready to be done by the Curl multi manager
520    identified by 'curlMultiP'.  This typically is transferring data on
521    an HTTP connection because the server is ready.
522 
523    For each transaction for which the multi manager finishes all the
524    required work, complete the transaction by calling its
525    "finish" routine.
526 
527    Return *transStillRunningP false if this work completes all of the
528    manager's transactions so that there is no reason to call us ever
529    again.
530 -----------------------------------------------------------------------------*/
531     bool immediateWorkToDo;
532     int runningHandles;
533 
534     immediateWorkToDo = true;  /* initial assumption */
535 
536     while (immediateWorkToDo && !envP->fault_occurred) {
537         curlMulti_perform(envP, curlMultiP,
538                           &immediateWorkToDo, &runningHandles);
539     }
540 
541     /* We either did all the work that's ready to do or hit an error. */
542 
543     if (!envP->fault_occurred) {
544         /* The work we did may have resulted in asynchronous messages
545            (asynchronous to the thing they refer to, not to us, of course).
546            In particular the message "Curl transaction has completed".
547            So we process those now.
548         */
549         processCurlMessages(envP, curlMultiP);
550 
551         *transStillRunningP = runningHandles > 0;
552     }
553 }
554 
555 
556 
557 static void
finishCurlMulti(xmlrpc_env * const envP,curlMulti * const curlMultiP,xmlrpc_timeoutType const timeoutType,xmlrpc_timespec const deadline,int * const interruptP)558 finishCurlMulti(xmlrpc_env *       const envP,
559                 curlMulti *        const curlMultiP,
560                 xmlrpc_timeoutType const timeoutType,
561                 xmlrpc_timespec    const deadline,
562                 int *              const interruptP) {
563 /*----------------------------------------------------------------------------
564    Prosecute all the Curl transactions under the control of
565    *curlMultiP.  E.g. send data if server is ready to take it, get
566    data if server has sent some, wind up the transaction if it is
567    done.
568 
569    Don't return until all the Curl transactions are done or we time out.
570 
571    The *interruptP flag alone will not interrupt us.  We will wait in
572    spite of it for all Curl transactions to complete.  *interruptP
573    just gives us a hint that the Curl transactions are being
574    interrupted, so we know there is work to do for them.  (The way it
575    works is Caller sets up a "progress" function that checks the same
576    interrupt flag and reports "kill me."  When we see the interrupt
577    flag, we call that progress function and get the message).
578 -----------------------------------------------------------------------------*/
579     bool rpcStillRunning;
580     bool timedOut;
581 
582     rpcStillRunning = true;  /* initial assumption */
583     timedOut = false;
584 
585     while (rpcStillRunning && !timedOut && !envP->fault_occurred) {
586 
587         if (interruptP) {
588             waitForWorkInt(envP, curlMultiP, timeoutType, deadline,
589                            interruptP);
590         } else
591             waitForWork(envP, curlMultiP, timeoutType, deadline, NULL);
592 
593         if (!envP->fault_occurred) {
594             xmlrpc_timespec nowTime;
595 
596             /* doCurlWork() (among other things) finds Curl
597                transactions that user wants to abort and finishes
598                them.
599             */
600             doCurlWork(envP, curlMultiP, &rpcStillRunning);
601 
602             xmlrpc_gettimeofday(&nowTime);
603 
604             timedOut = (timeoutType == timeout_yes &&
605                         timeIsAfter(nowTime, deadline));
606         }
607     }
608 }
609 
610 
611 
612 static void
getTimeoutParm(xmlrpc_env * const envP,const struct xmlrpc_curl_xportparms * const curlXportParmsP,size_t const parmSize,unsigned int * const timeoutP)613 getTimeoutParm(xmlrpc_env *                          const envP,
614                const struct xmlrpc_curl_xportparms * const curlXportParmsP,
615                size_t                                const parmSize,
616                unsigned int *                        const timeoutP) {
617 
618     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(timeout))
619         *timeoutP = 0;
620     else {
621         if (curlHasNosignal()) {
622             /* libcurl takes a 'long' in milliseconds for the timeout value */
623             if ((unsigned)(long)(curlXportParmsP->timeout) !=
624                 curlXportParmsP->timeout)
625                 xmlrpc_faultf(envP, "Timeout value %u is too large.",
626                               curlXportParmsP->timeout);
627             else
628                 *timeoutP = curlXportParmsP->timeout;
629         } else
630             xmlrpc_faultf(envP, "You cannot specify a 'timeout' parameter "
631                           "because the Curl library is too old and is not "
632                           "capable of doing timeouts except by using "
633                           "signals.  You need at least Curl 7.10");
634     }
635 }
636 
637 
638 
639 static void
setVerbose(bool * const verboseP)640 setVerbose(bool * const verboseP) {
641 
642     const char * const xmlrpcTraceCurl = getenv("XMLRPC_TRACE_CURL");
643 
644     if (xmlrpcTraceCurl)
645         *verboseP = true;
646     else
647         *verboseP = false;
648 }
649 
650 
651 
652 static void
getXportParms(xmlrpc_env * const envP,const struct xmlrpc_curl_xportparms * const curlXportParmsP,size_t const parmSize,struct xmlrpc_client_transport * const transportP)653 getXportParms(xmlrpc_env *                          const envP,
654               const struct xmlrpc_curl_xportparms * const curlXportParmsP,
655               size_t                                const parmSize,
656               struct xmlrpc_client_transport *      const transportP) {
657 /*----------------------------------------------------------------------------
658    Get the parameters out of *curlXportParmsP and update *transportP
659    to reflect them.
660 
661    *curlXportParmsP is a 'parmSize' bytes long prefix of
662    struct xmlrpc_curl_xportparms.
663 
664    curlXportParmsP is something the user created.  It's designed to be
665    friendly to the user, not to this program, and is encumbered by
666    lots of backward compatibility constraints.  In particular, the
667    user may have coded and/or compiled it at a time that struct
668    xmlrpc_curl_xportparms was smaller than it is now!
669 
670    Also, the user might have specified something invalid.
671 
672    So that's why we don't simply attach a copy of *curlXportParmsP to
673    *transportP.
674 
675    To the extent that *curlXportParmsP is too small to contain a parameter,
676    we return the default value for that parameter.
677 
678    Special case:  curlXportParmsP == NULL means there is no input at all.
679    In that case, we return default values for everything.
680 -----------------------------------------------------------------------------*/
681     struct curlSetup * const curlSetupP = &transportP->curlSetupStuff;
682 
683     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(user_agent))
684         transportP->userAgent = NULL;
685     else if (curlXportParmsP->user_agent == NULL)
686         transportP->userAgent = NULL;
687     else
688         transportP->userAgent = strdup(curlXportParmsP->user_agent);
689 
690     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(dont_advertise))
691         transportP->dontAdvertise = false;
692     else
693         transportP->dontAdvertise = curlXportParmsP->dont_advertise;
694 
695     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(network_interface))
696         curlSetupP->networkInterface = NULL;
697     else if (curlXportParmsP->network_interface == NULL)
698         curlSetupP->networkInterface = NULL;
699     else
700         curlSetupP->networkInterface =
701             strdup(curlXportParmsP->network_interface);
702 
703     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(no_ssl_verifypeer))
704         curlSetupP->sslVerifyPeer = true;
705     else
706         curlSetupP->sslVerifyPeer = !curlXportParmsP->no_ssl_verifypeer;
707 
708     if (!curlXportParmsP ||
709         parmSize < XMLRPC_CXPSIZE(no_ssl_verifyhost))
710         curlSetupP->sslVerifyHost = true;
711     else
712         curlSetupP->sslVerifyHost = !curlXportParmsP->no_ssl_verifyhost;
713 
714     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(ssl_cert))
715         curlSetupP->sslCert = NULL;
716     else if (curlXportParmsP->ssl_cert == NULL)
717         curlSetupP->sslCert = NULL;
718     else
719         curlSetupP->sslCert = strdup(curlXportParmsP->ssl_cert);
720 
721     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslcerttype))
722         curlSetupP->sslCertType = NULL;
723     else if (curlXportParmsP->sslcerttype == NULL)
724         curlSetupP->sslCertType = NULL;
725     else
726         curlSetupP->sslCertType = strdup(curlXportParmsP->sslcerttype);
727 
728     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslcertpasswd))
729         curlSetupP->sslCertPasswd = NULL;
730     else if (curlXportParmsP->sslcertpasswd == NULL)
731         curlSetupP->sslCertPasswd = NULL;
732     else
733         curlSetupP->sslCertPasswd = strdup(curlXportParmsP->sslcertpasswd);
734 
735     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslkey))
736         curlSetupP->sslKey = NULL;
737     else if (curlXportParmsP->sslkey == NULL)
738         curlSetupP->sslKey = NULL;
739     else
740         curlSetupP->sslKey = strdup(curlXportParmsP->sslkey);
741 
742     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslkeytype))
743         curlSetupP->sslKeyType = NULL;
744     else if (curlXportParmsP->sslkeytype == NULL)
745         curlSetupP->sslKeyType = NULL;
746     else
747         curlSetupP->sslKeyType = strdup(curlXportParmsP->sslkeytype);
748 
749     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslkeypasswd))
750         curlSetupP->sslKeyPasswd = NULL;
751     else if (curlXportParmsP->sslkeypasswd == NULL)
752         curlSetupP->sslKeyPasswd = NULL;
753     else
754         curlSetupP->sslKeyPasswd = strdup(curlXportParmsP->sslkeypasswd);
755 
756     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslengine))
757         curlSetupP->sslEngine = NULL;
758     else if (curlXportParmsP->sslengine == NULL)
759         curlSetupP->sslEngine = NULL;
760     else
761         curlSetupP->sslEngine = strdup(curlXportParmsP->sslengine);
762 
763     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslengine_default))
764         curlSetupP->sslEngineDefault = false;
765     else
766         curlSetupP->sslEngineDefault = !!curlXportParmsP->sslengine_default;
767 
768     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslversion))
769         curlSetupP->sslVersion = XMLRPC_SSLVERSION_DEFAULT;
770     else
771         curlSetupP->sslVersion = curlXportParmsP->sslversion;
772 
773     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(cainfo))
774         curlSetupP->caInfo = NULL;
775     else if (curlXportParmsP->cainfo == NULL)
776         curlSetupP->caInfo = NULL;
777     else
778         curlSetupP->caInfo = strdup(curlXportParmsP->cainfo);
779 
780     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(capath))
781         curlSetupP->caPath = NULL;
782     else if (curlXportParmsP->capath == NULL)
783         curlSetupP->caPath = NULL;
784     else
785         curlSetupP->caPath = strdup(curlXportParmsP->capath);
786 
787     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(randomfile))
788         curlSetupP->randomFile = NULL;
789     else if (curlXportParmsP->randomfile == NULL)
790         curlSetupP->randomFile = NULL;
791     else
792         curlSetupP->randomFile = strdup(curlXportParmsP->randomfile);
793 
794     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(egdsocket))
795         curlSetupP->egdSocket = NULL;
796     else if (curlXportParmsP->egdsocket == NULL)
797         curlSetupP->egdSocket = NULL;
798     else
799         curlSetupP->egdSocket = strdup(curlXportParmsP->egdsocket);
800 
801     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(ssl_cipher_list))
802         curlSetupP->sslCipherList = NULL;
803     else if (curlXportParmsP->ssl_cipher_list == NULL)
804         curlSetupP->sslCipherList = NULL;
805     else
806         curlSetupP->sslCipherList = strdup(curlXportParmsP->ssl_cipher_list);
807 
808     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(proxy))
809         curlSetupP->proxy = NULL;
810     else if (curlXportParmsP->proxy == NULL)
811         curlSetupP->proxy = NULL;
812     else
813         curlSetupP->proxy = strdup(curlXportParmsP->proxy);
814 
815     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(proxy_port))
816         curlSetupP->proxyPort = 8080;
817     else
818         curlSetupP->proxyPort = curlXportParmsP->proxy_port;
819 
820     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(proxy_auth))
821         curlSetupP->proxyAuth = CURLAUTH_BASIC;
822     else
823         curlSetupP->proxyAuth = curlXportParmsP->proxy_auth;
824 
825     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(proxy_userpwd))
826         curlSetupP->proxyUserPwd = NULL;
827     else if (curlXportParmsP->proxy_userpwd == NULL)
828         curlSetupP->proxyUserPwd = NULL;
829     else
830         curlSetupP->proxyUserPwd = strdup(curlXportParmsP->proxy_userpwd);
831 
832     if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(proxy_type))
833         curlSetupP->proxyType = CURLPROXY_HTTP;
834     else
835         curlSetupP->proxyType = curlXportParmsP->proxy_type;
836 
837     getTimeoutParm(envP, curlXportParmsP, parmSize, &curlSetupP->timeout);
838 }
839 
840 
841 
842 static void
freeXportParms(const struct xmlrpc_client_transport * const transportP)843 freeXportParms(const struct xmlrpc_client_transport * const transportP) {
844 
845     const struct curlSetup * const curlSetupP = &transportP->curlSetupStuff;
846 
847     if (curlSetupP->sslCipherList)
848         xmlrpc_strfree(curlSetupP->sslCipherList);
849     if (curlSetupP->egdSocket)
850         xmlrpc_strfree(curlSetupP->egdSocket);
851     if (curlSetupP->randomFile)
852         xmlrpc_strfree(curlSetupP->randomFile);
853     if (curlSetupP->caPath)
854         xmlrpc_strfree(curlSetupP->caPath);
855     if (curlSetupP->caInfo)
856         xmlrpc_strfree(curlSetupP->caInfo);
857     if (curlSetupP->sslEngine)
858         xmlrpc_strfree(curlSetupP->sslEngine);
859     if (curlSetupP->sslKeyPasswd)
860         xmlrpc_strfree(curlSetupP->sslKeyPasswd);
861     if (curlSetupP->sslKeyType)
862         xmlrpc_strfree(curlSetupP->sslKeyType);
863     if (curlSetupP->sslKey)
864         xmlrpc_strfree(curlSetupP->sslKey);
865     if (curlSetupP->sslCertPasswd)
866         xmlrpc_strfree(curlSetupP->sslCertPasswd);
867     if (curlSetupP->sslCertType)
868         xmlrpc_strfree(curlSetupP->sslCertType);
869     if (curlSetupP->sslCert)
870         xmlrpc_strfree(curlSetupP->sslCert);
871     if (curlSetupP->networkInterface)
872         xmlrpc_strfree(curlSetupP->networkInterface);
873     if (transportP->userAgent)
874         xmlrpc_strfree(transportP->userAgent);
875     if (curlSetupP->proxy)
876         xmlrpc_strfree(curlSetupP->proxy);
877     if (curlSetupP->proxyUserPwd)
878         xmlrpc_strfree(curlSetupP->proxyUserPwd);
879 }
880 
881 
882 
883 static void
createSyncCurlSession(xmlrpc_env * const envP,CURL ** const curlSessionPP)884 createSyncCurlSession(xmlrpc_env * const envP,
885                       CURL **      const curlSessionPP) {
886 /*----------------------------------------------------------------------------
887    Create a Curl session to be used for multiple serial transactions.
888    The Curl session we create is not complete -- it still has to be
889    further set up for each particular transaction.
890 
891    We can't set up anything here that changes from one transaction to the
892    next.
893 
894    We don't bother setting up anything that has to be set up for an
895    asynchronous transaction because code that is common between synchronous
896    and asynchronous transactions takes care of that anyway.
897 
898    That leaves things, such as cookies, that don't exist for
899    asynchronous transactions, and are common to multiple serial
900    synchronous transactions.
901 -----------------------------------------------------------------------------*/
902     CURL * const curlSessionP = curl_easy_init();
903 
904     if (curlSessionP == NULL)
905         xmlrpc_faultf(envP, "Could not create Curl session.  "
906                       "curl_easy_init() failed.");
907     else {
908         /* The following is a trick.  CURLOPT_COOKIEFILE is the name
909            of the file containing the initial cookies for the Curl
910            session.  But setting it is also what turns on the cookie
911            function itself, whereby the Curl library accepts and
912            stores cookies from the server and sends them back on
913            future requests.  We don't have a file of initial cookies, but
914            we want to turn on cookie function, so we set the option to
915            something we know does not validly name a file.  Curl will
916            ignore the error and just start up cookie function with no
917            initial cookies.
918         */
919         curl_easy_setopt(curlSessionP, CURLOPT_COOKIEFILE, "");
920 
921         *curlSessionPP = curlSessionP;
922     }
923 }
924 
925 
926 
927 static void
destroySyncCurlSession(CURL * const curlSessionP)928 destroySyncCurlSession(CURL * const curlSessionP) {
929 
930     curl_easy_cleanup(curlSessionP);
931 }
932 
933 
934 
935 static void
makeSyncCurlSession(xmlrpc_env * const envP,struct xmlrpc_client_transport * const transportP)936 makeSyncCurlSession(xmlrpc_env *                     const envP,
937                     struct xmlrpc_client_transport * const transportP) {
938 
939     transportP->syncCurlSessionLockP = curlLock_create_pthread();
940     if (transportP->syncCurlSessionLockP == NULL)
941         xmlrpc_faultf(envP, "Unable to create lock for "
942                       "synchronous Curl session.");
943     else {
944         createSyncCurlSession(envP, &transportP->syncCurlSessionP);
945 
946         if (!envP->fault_occurred) {
947             /* We'll need a multi manager to actually execute this session: */
948             transportP->syncCurlMultiP = curlMulti_create();
949 
950             if (transportP->syncCurlMultiP == NULL)
951                 xmlrpc_faultf(envP, "Unable to create Curl multi manager for "
952                               "synchronous RPCs");
953 
954             if (envP->fault_occurred)
955                 destroySyncCurlSession(transportP->syncCurlSessionP);
956         }
957         if (envP->fault_occurred)
958             transportP->syncCurlSessionLockP->destroy(
959                 transportP->syncCurlSessionLockP);
960     }
961 }
962 
963 
964 
965 static void
unmakeSyncCurlSession(struct xmlrpc_client_transport * const transportP)966 unmakeSyncCurlSession(struct xmlrpc_client_transport * const transportP) {
967 
968     curlMulti_destroy(transportP->syncCurlMultiP);
969 
970     destroySyncCurlSession(transportP->syncCurlSessionP);
971 
972     transportP->syncCurlSessionLockP->destroy(
973         transportP->syncCurlSessionLockP);
974 }
975 
976 
977 
978 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 void * const transportparmsP,size_t const parm_size,struct xmlrpc_client_transport ** const handlePP)979 create(xmlrpc_env *                      const envP,
980        int                               const flags ATTR_UNUSED,
981        const char *                      const appname ATTR_UNUSED,
982        const char *                      const appversion ATTR_UNUSED,
983        const void *                      const transportparmsP,
984        size_t                            const parm_size,
985        struct xmlrpc_client_transport ** const handlePP) {
986 /*----------------------------------------------------------------------------
987    This does the 'create' operation for a Curl client transport.
988 -----------------------------------------------------------------------------*/
989     const struct xmlrpc_curl_xportparms * const curlXportParmsP =
990         transportparmsP;
991 
992     struct xmlrpc_client_transport * transportP;
993 
994     MALLOCVAR(transportP);
995     if (transportP == NULL)
996         xmlrpc_faultf(envP, "Unable to allocate transport descriptor.");
997     else {
998         setVerbose(&transportP->curlSetupStuff.verbose);
999 
1000         transportP->interruptP = NULL;
1001 
1002         transportP->asyncCurlMultiP = curlMulti_create();
1003 
1004         if (transportP->asyncCurlMultiP == NULL)
1005             xmlrpc_faultf(envP, "Unable to create Curl multi manager for "
1006                           "asynchronous RPCs");
1007         else {
1008             getXportParms(envP, curlXportParmsP, parm_size, transportP);
1009 
1010             if (!envP->fault_occurred) {
1011                 makeSyncCurlSession(envP, transportP);
1012 
1013                 if (envP->fault_occurred)
1014                     freeXportParms(transportP);
1015             }
1016             if (envP->fault_occurred)
1017                 curlMulti_destroy(transportP->asyncCurlMultiP);
1018         }
1019         if (envP->fault_occurred)
1020             free(transportP);
1021     }
1022     *handlePP = transportP;
1023 }
1024 
1025 
1026 
1027 static void
setInterrupt(struct xmlrpc_client_transport * const clientTransportP,int * const interruptP)1028 setInterrupt(struct xmlrpc_client_transport * const clientTransportP,
1029              int *                            const interruptP) {
1030 
1031     clientTransportP->interruptP = interruptP;
1032 }
1033 
1034 
1035 
1036 static void
assertNoOutstandingCurlWork(curlMulti * const curlMultiP)1037 assertNoOutstandingCurlWork(curlMulti * const curlMultiP) {
1038 
1039     xmlrpc_env env;
1040     bool immediateWorkToDo;
1041     int runningHandles;
1042 
1043     xmlrpc_env_init(&env);
1044 
1045     curlMulti_perform(&env, curlMultiP, &immediateWorkToDo, &runningHandles);
1046 
1047     /* We know the above was a no-op, since we're asserting that there
1048        is no outstanding work.
1049     */
1050     XMLRPC_ASSERT(!env.fault_occurred);
1051     XMLRPC_ASSERT(!immediateWorkToDo);
1052     XMLRPC_ASSERT(runningHandles == 0);
1053     xmlrpc_env_clean(&env);
1054 }
1055 
1056 
1057 
1058 static void
destroy(struct xmlrpc_client_transport * const clientTransportP)1059 destroy(struct xmlrpc_client_transport * const clientTransportP) {
1060 /*----------------------------------------------------------------------------
1061    This does the 'destroy' operation for a Curl client transport.
1062 
1063    An RPC is a reference to a client XML transport, so you may not
1064    destroy a transport while RPCs are running.  To ensure no
1065    asynchronous RPCs are running, you must successfully execute the
1066    transport 'finishAsync' method, with no interruptions or timeouts
1067    allowed.  To speed that up, you can set the transport's interrupt
1068    flag to 1 first, which will make all outstanding RPCs fail
1069    immediately.
1070 -----------------------------------------------------------------------------*/
1071     XMLRPC_ASSERT(clientTransportP != NULL);
1072 
1073     assertNoOutstandingCurlWork(clientTransportP->asyncCurlMultiP);
1074         /* We know this is true because a condition of destroying the
1075            transport is that there be no outstanding asynchronous RPCs.
1076         */
1077     assertNoOutstandingCurlWork(clientTransportP->syncCurlMultiP);
1078         /* This is because a condition of destroying the transport is
1079            that no transport method be running.  The only way a
1080            synchronous RPC can be in progress is for the 'perform' method
1081            to be running.
1082         */
1083 
1084     unmakeSyncCurlSession(clientTransportP);
1085 
1086     curlMulti_destroy(clientTransportP->asyncCurlMultiP);
1087 
1088     freeXportParms(clientTransportP);
1089 
1090     free(clientTransportP);
1091 }
1092 
1093 
1094 
1095 static void
performCurlTransaction(xmlrpc_env * const envP,curlTransaction * const curlTransactionP,curlMulti * const curlMultiP,int * const interruptP)1096 performCurlTransaction(xmlrpc_env *      const envP,
1097                        curlTransaction * const curlTransactionP,
1098                        curlMulti *       const curlMultiP,
1099                        int *             const interruptP) {
1100 
1101     curlMulti_addHandle(envP, curlMultiP,
1102                         curlTransaction_curlSession(curlTransactionP));
1103 
1104     /* Failure here just means something screwy in the multi manager;
1105        Above does not even begin to perform the HTTP transaction
1106     */
1107 
1108     if (!envP->fault_occurred) {
1109         xmlrpc_timespec const dummy = {0,0};
1110 
1111         finishCurlMulti(envP, curlMultiP, timeout_no, dummy, interruptP);
1112 
1113         /* Failure here just means something screwy in the multi
1114            manager; any failure of the HTTP transaction would have been
1115            recorded in *curlTransactionP.
1116         */
1117 
1118         if (!envP->fault_occurred) {
1119             /* Curl session completed OK.  But did HTTP transaction
1120                work?
1121             */
1122             curlTransaction_getError(curlTransactionP, envP);
1123         }
1124         /* If the CURL transaction is still going, removing the handle
1125            here aborts it.  At least it's supposed to.  From what I've
1126            seen in the Curl code in 2007, I don't think it does.  I
1127            couldn't get Curl maintainers interested in the problem,
1128            except to say, "If you're right, there's a bug."
1129         */
1130         curlMulti_removeHandle(curlMultiP,
1131                                curlTransaction_curlSession(curlTransactionP));
1132     }
1133 }
1134 
1135 
1136 
1137 static void
startRpc(xmlrpc_env * const envP,rpc * const rpcP)1138 startRpc(xmlrpc_env * const envP,
1139          rpc *        const rpcP) {
1140 
1141     curlMulti_addHandle(envP,
1142                         rpcP->transportP->asyncCurlMultiP,
1143                         curlTransaction_curlSession(rpcP->curlTransactionP));
1144 }
1145 
1146 
1147 
1148 static curlt_finishFn   finishRpcCurlTransaction;
1149 static curlt_progressFn curlTransactionProgress;
1150 
1151 
1152 
1153 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,xmlrpc_transport_progress progress,struct xmlrpc_call_info * const callInfoP,rpc ** const rpcPP)1154 createRpc(xmlrpc_env *                     const envP,
1155           struct xmlrpc_client_transport * const clientTransportP,
1156           CURL *                           const curlSessionP,
1157           const xmlrpc_server_info *       const serverP,
1158           xmlrpc_mem_block *               const callXmlP,
1159           xmlrpc_mem_block *               const responseXmlP,
1160           xmlrpc_transport_asynch_complete       complete,
1161           xmlrpc_transport_progress              progress,
1162           struct xmlrpc_call_info *        const callInfoP,
1163           rpc **                           const rpcPP) {
1164 
1165     rpc * rpcP;
1166 
1167     MALLOCVAR(rpcP);
1168     if (rpcP == NULL)
1169         xmlrpc_faultf(envP, "Couldn't allocate memory for rpc object");
1170     else {
1171         rpcP->transportP   = clientTransportP;
1172         rpcP->curlSessionP = curlSessionP;
1173         rpcP->callInfoP    = callInfoP;
1174         rpcP->complete     = complete;
1175         rpcP->progress     = progress;
1176         rpcP->responseXmlP = responseXmlP;
1177 
1178         curlTransaction_create(envP,
1179                                curlSessionP,
1180                                serverP,
1181                                callXmlP, responseXmlP,
1182                                clientTransportP->dontAdvertise,
1183                                clientTransportP->userAgent,
1184                                &clientTransportP->curlSetupStuff,
1185                                rpcP,
1186                                complete ? &finishRpcCurlTransaction : NULL,
1187                                progress ? &curlTransactionProgress : NULL,
1188                                &rpcP->curlTransactionP);
1189         if (!envP->fault_occurred) {
1190             if (envP->fault_occurred)
1191                 curlTransaction_destroy(rpcP->curlTransactionP);
1192         }
1193         if (envP->fault_occurred)
1194             free(rpcP);
1195     }
1196     *rpcPP = rpcP;
1197 }
1198 
1199 
1200 
1201 static void
destroyRpc(rpc * const rpcP)1202 destroyRpc(rpc * const rpcP) {
1203 
1204     XMLRPC_ASSERT_PTR_OK(rpcP);
1205 
1206     curlTransaction_destroy(rpcP->curlTransactionP);
1207 
1208     free(rpcP);
1209 }
1210 
1211 
1212 
1213 static void
performRpc(xmlrpc_env * const envP,rpc * const rpcP,curlMulti * const curlMultiP,int * const interruptP)1214 performRpc(xmlrpc_env * const envP,
1215            rpc *        const rpcP,
1216            curlMulti *  const curlMultiP,
1217            int *        const interruptP) {
1218 
1219     performCurlTransaction(envP, rpcP->curlTransactionP, curlMultiP,
1220                            interruptP);
1221 }
1222 
1223 
1224 
1225 static curlt_finishFn finishRpcCurlTransaction;
1226 
1227 static void
finishRpcCurlTransaction(xmlrpc_env * const envP ATTR_UNUSED,void * const userContextP)1228 finishRpcCurlTransaction(xmlrpc_env * const envP ATTR_UNUSED,
1229                          void *       const userContextP) {
1230 /*----------------------------------------------------------------------------
1231   Handle the event that a Curl transaction for an asynchronous RPC has
1232   completed on the Curl session identified by 'curlSessionP'.
1233 
1234   Tell the requester of the RPC the results.
1235 
1236   Remove the Curl session from its Curl multi manager and destroy the
1237   Curl session, the XML response buffer, the Curl transaction, and the RPC.
1238 -----------------------------------------------------------------------------*/
1239     rpc * const rpcP = userContextP;
1240     curlTransaction * const curlTransactionP = rpcP->curlTransactionP;
1241     struct xmlrpc_client_transport * const transportP = rpcP->transportP;
1242 
1243     curlMulti_removeHandle(transportP->asyncCurlMultiP,
1244                            curlTransaction_curlSession(curlTransactionP));
1245 
1246     {
1247         xmlrpc_env env;
1248 
1249         xmlrpc_env_init(&env);
1250 
1251         curlTransaction_getError(curlTransactionP, &env);
1252 
1253         rpcP->complete(rpcP->callInfoP, rpcP->responseXmlP, env);
1254 
1255         xmlrpc_env_clean(&env);
1256     }
1257 
1258     curl_easy_cleanup(rpcP->curlSessionP);
1259 
1260     XMLRPC_MEMBLOCK_FREE(char, rpcP->responseXmlP);
1261 
1262     destroyRpc(rpcP);
1263 }
1264 
1265 
1266 
1267 static curlt_progressFn curlTransactionProgress;
1268 
1269 static void
curlTransactionProgress(void * const context,double const dlTotal,double const dlNow,double const ulTotal,double const ulNow,bool * const abortP)1270 curlTransactionProgress(void * const context,
1271                         double const dlTotal,
1272                         double const dlNow,
1273                         double const ulTotal,
1274                         double const ulNow,
1275                         bool * const abortP) {
1276 /*----------------------------------------------------------------------------
1277    This is equivalent to a Curl "progress function" (the curlTransaction
1278    object just passes through the call from libcurl).
1279 
1280    The curlTransaction calls this once a second telling us how much
1281    data has transferred.  If the transport user has set up a progress
1282    function, we call that with this progress information.  That
1283    function might e.g. display a progress bar.
1284 
1285    Additionally, the curlTransaction gives us the opportunity to tell it
1286    to abort the transaction, which we do if the user has set his
1287    "interrupt" flag (which he registered with the transport when he
1288    created it).
1289 -----------------------------------------------------------------------------*/
1290     rpc * const rpcP = context;
1291     struct xmlrpc_client_transport * const transportP = rpcP->transportP;
1292 
1293     struct xmlrpc_progress_data progressData;
1294 
1295     assert(rpcP);
1296     assert(transportP);
1297     assert(rpcP->progress);
1298 
1299     progressData.response.total = dlTotal;
1300     progressData.response.now   = dlNow;
1301     progressData.call.total     = ulTotal;
1302     progressData.call.now       = ulNow;
1303 
1304     rpcP->progress(rpcP->callInfoP, progressData);
1305 
1306     if (transportP->interruptP)
1307         *abortP = *transportP->interruptP;
1308     else
1309         *abortP = false;
1310 }
1311 
1312 
1313 
1314 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,xmlrpc_transport_progress progress,struct xmlrpc_call_info * const callInfoP)1315 sendRequest(xmlrpc_env *                     const envP,
1316             struct xmlrpc_client_transport * const clientTransportP,
1317             const xmlrpc_server_info *       const serverP,
1318             xmlrpc_mem_block *               const callXmlP,
1319             xmlrpc_transport_asynch_complete       complete,
1320             xmlrpc_transport_progress              progress,
1321             struct xmlrpc_call_info *        const callInfoP) {
1322 /*----------------------------------------------------------------------------
1323    Initiate an XML-RPC rpc asynchronously.  Don't wait for it to go to
1324    the server.
1325 
1326    Unless we return failure, we arrange to have complete() called when
1327    the rpc completes.
1328 
1329    This does the 'send_request' operation for a Curl client transport.
1330 -----------------------------------------------------------------------------*/
1331     rpc * rpcP;
1332     xmlrpc_mem_block * responseXmlP;
1333 
1334     responseXmlP = XMLRPC_MEMBLOCK_NEW(char, envP, 0);
1335     if (!envP->fault_occurred) {
1336         CURL * const curlSessionP = curl_easy_init();
1337 
1338         if (curlSessionP == NULL)
1339             xmlrpc_faultf(envP, "Could not create Curl session.  "
1340                           "curl_easy_init() failed.");
1341         else {
1342             createRpc(envP, clientTransportP, curlSessionP, serverP,
1343                       callXmlP, responseXmlP, complete, progress, callInfoP,
1344                       &rpcP);
1345 
1346             if (!envP->fault_occurred) {
1347                 startRpc(envP, rpcP);
1348 
1349                 if (envP->fault_occurred)
1350                     destroyRpc(rpcP);
1351             }
1352             if (envP->fault_occurred)
1353                 curl_easy_cleanup(curlSessionP);
1354         }
1355         if (envP->fault_occurred)
1356             XMLRPC_MEMBLOCK_FREE(char, responseXmlP);
1357     }
1358     /* If we're returning success, the user's eventual finish_asynch
1359        call will destroy this RPC, Curl session, and response buffer
1360        and remove the Curl session from the Curl multi manager.
1361        (If we're returning failure, we didn't create any of those).
1362     */
1363 }
1364 
1365 
1366 
1367 static void
finishAsynch(struct xmlrpc_client_transport * const clientTransportP,xmlrpc_timeoutType const timeoutType,xmlrpc_timeout const timeout)1368 finishAsynch(
1369     struct xmlrpc_client_transport * const clientTransportP,
1370     xmlrpc_timeoutType               const timeoutType,
1371     xmlrpc_timeout                   const timeout) {
1372 /*----------------------------------------------------------------------------
1373    Wait for the Curl multi manager to finish the Curl transactions for
1374    all outstanding RPCs and destroy those RPCs.
1375 
1376    But give up if a) too much time passes as defined by 'timeoutType'
1377    and 'timeout'; or b) the transport client requests interruption
1378    (i.e. the transport's interrupt flag becomes nonzero).  Normally, a
1379    signal must get our attention for us to notice the interrupt flag.
1380 
1381    This does the 'finish_asynch' operation for a Curl client transport.
1382 
1383    It would be cool to replace this with something analogous to the
1384    Curl asynchronous interface: Have something like curl_multi_fdset()
1385    that returns a bunch of file descriptors on which the user can wait
1386    (along with possibly other file descriptors of his own) and
1387    something like curl_multi_perform() to finish whatever RPCs are
1388    ready to finish at that moment.  The implementation would be little
1389    more than wrapping curl_multi_fdset() and curl_multi_perform().
1390 
1391    Note that the user can call this multiple times, due to timeouts,
1392    but must eventually call it once with no timeout so he
1393    knows that all the RPCs are finished.  Either that or terminate the
1394    process so it doesn't matter if RPCs are still going.
1395 -----------------------------------------------------------------------------*/
1396     xmlrpc_env env;
1397 
1398     xmlrpc_timespec waitTimeoutTime;
1399         /* The datetime after which we should quit waiting */
1400 
1401     xmlrpc_env_init(&env);
1402 
1403     if (timeoutType == timeout_yes) {
1404         xmlrpc_timespec waitStartTime;
1405         xmlrpc_gettimeofday(&waitStartTime);
1406         addMilliseconds(waitStartTime, timeout, &waitTimeoutTime);
1407     }
1408 
1409     finishCurlMulti(&env, clientTransportP->asyncCurlMultiP,
1410                     timeoutType, waitTimeoutTime,
1411                     clientTransportP->interruptP);
1412 
1413     /* If the above fails, it is catastrophic, because it means there is
1414        no way to complete outstanding Curl transactions and RPCs, and
1415        no way to release their resources.
1416 
1417        We should at least expand this interface some day to push the
1418        problem back up to the user, but for now we just do this Hail Mary
1419        response.
1420 
1421        Note that a failure of finish_curlMulti() does not mean that
1422        a session completed with an error or an RPC completed with an
1423        error.  Those things are reported up through the user's
1424        xmlrpc_transport_asynch_complete routine.  A failure here is
1425        something that stopped us from calling that.
1426 
1427        Note that a timeout causes a successful completion,
1428        but without finishing all the RPCs!
1429     */
1430 
1431     if (env.fault_occurred)
1432         fprintf(stderr, "finishAsync() failed.  Xmlrpc-c Curl transport "
1433                 "is now in an unknown state and may not be able to "
1434                 "continue functioning.  Specifics of the failure: %s\n",
1435                 env.fault_string);
1436 
1437     xmlrpc_env_clean(&env);
1438 }
1439 
1440 
1441 
1442 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)1443 call(xmlrpc_env *                     const envP,
1444      struct xmlrpc_client_transport * const clientTransportP,
1445      const xmlrpc_server_info *       const serverP,
1446      xmlrpc_mem_block *               const callXmlP,
1447      xmlrpc_mem_block **              const responseXmlPP) {
1448 
1449     xmlrpc_mem_block * responseXmlP;
1450     rpc * rpcP;
1451 
1452     XMLRPC_ASSERT_ENV_OK(envP);
1453     XMLRPC_ASSERT_PTR_OK(serverP);
1454     XMLRPC_ASSERT_PTR_OK(callXmlP);
1455     XMLRPC_ASSERT_PTR_OK(responseXmlPP);
1456 
1457     responseXmlP = XMLRPC_MEMBLOCK_NEW(char, envP, 0);
1458     if (!envP->fault_occurred) {
1459         /* Only one RPC at a time can use a Curl session, so we have to
1460            hold the lock as long as our RPC exists.
1461         */
1462         lockSyncCurlSession(clientTransportP);
1463         createRpc(envP, clientTransportP, clientTransportP->syncCurlSessionP,
1464                   serverP,
1465                   callXmlP, responseXmlP,
1466                   NULL, NULL, NULL,
1467                   &rpcP);
1468 
1469         if (!envP->fault_occurred) {
1470             performRpc(envP, rpcP, clientTransportP->syncCurlMultiP,
1471                        clientTransportP->interruptP);
1472 
1473             *responseXmlPP = responseXmlP;
1474 
1475             destroyRpc(rpcP);
1476         }
1477         unlockSyncCurlSession(clientTransportP);
1478         if (envP->fault_occurred)
1479             XMLRPC_MEMBLOCK_FREE(char, responseXmlP);
1480     }
1481 }
1482 
1483 
1484 
1485 static void
setupGlobalConstants(xmlrpc_env * const envP)1486 setupGlobalConstants(xmlrpc_env * const envP) {
1487 /*----------------------------------------------------------------------------
1488    See longwinded discussion of the global constant issue at the top of
1489    this file.
1490 -----------------------------------------------------------------------------*/
1491     initWindowsStuff(envP);
1492 
1493     if (!envP->fault_occurred) {
1494         CURLcode rc;
1495 
1496         rc = curl_global_init(CURL_GLOBAL_ALL);
1497 
1498         if (rc != CURLE_OK)
1499             xmlrpc_faultf(envP, "curl_global_init() failed with code %d", rc);
1500     }
1501 }
1502 
1503 
1504 
1505 static void
teardownGlobalConstants(void)1506 teardownGlobalConstants(void) {
1507 /*----------------------------------------------------------------------------
1508    See longwinded discussionof the global constant issue at the top of
1509    this file.
1510 -----------------------------------------------------------------------------*/
1511     curl_global_cleanup();
1512 
1513     termWindowsStuff();
1514 }
1515 
1516 
1517 
1518 struct xmlrpc_client_transport_ops xmlrpc_curl_transport_ops = {
1519     &setupGlobalConstants,
1520     &teardownGlobalConstants,
1521     &create,
1522     &destroy,
1523     &sendRequest,
1524     &call,
1525     &finishAsynch,
1526     &setInterrupt,
1527 };
1528