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