1 /*=============================================================================
3 ===============================================================================
4 Curl-based client transport for Xmlrpc-c
6 By Bryan Henderson 04.12.10.
8 Contributed to the public domain by its author.
9 =============================================================================*/
11 /*----------------------------------------------------------------------------
12 Curl global variables:
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).
23 So our setup_global_const transport operation calls
24 curl_global_init() and our teardown_global_const calls
25 curl_global_cleanup().
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.
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.
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.
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 -----------------------------------------------------------------------------*/
61 #include "xmlrpc_config.h"
65 #include "mallocvar.h"
67 #include "girstring.h"
70 #include "xmlrpc-c/base.h"
71 #include "xmlrpc-c/base_int.h"
72 #include "xmlrpc-c/string_int.h"
73 #include "xmlrpc-c/client.h"
74 #include "xmlrpc-c/client_int.h"
75 #include "xmlrpc-c/transport.h"
78 #include <curl/curl.h>
79 #include <curl/types.h>
80 #include <curl/easy.h>
81 #include <curl/multi.h>
83 #if defined (WIN32) && defined(_DEBUG)
85 # define new DEBUG_NEW
86 # define malloc(size) _malloc_dbg( size, _NORMAL_BLOCK, __FILE__, __LINE__)
88 static char THIS_FILE[] = __FILE__;
89 #endif /*WIN32 && _DEBUG*/
95 /* This is all client transport properties that are implemented as
96 simple Curl session properties (i.e. the transport basically just
97 passes them through to Curl without looking at them).
99 People occasionally want to replace all this with something where
100 the Xmlrpc-c user simply does the curl_easy_setopt() call and this
101 code need not know about all these options. Unfortunately, that's
102 a significant modularity violation. Either the Xmlrpc-c user
103 controls the Curl object or he doesn't. If he does, then he
104 shouldn't use libxmlrpc_client -- he should just copy some of this
105 code into his own program. If he doesn't, then he should never see
108 Speaking of modularity: the only reason this is a separate struct
109 is to make the code easier to manage. Ideally, the fact that these
110 particular properties of the transport are implemented by simple
111 Curl session setup would be known only at the lowest level code
112 that does that setup.
115 const char * networkInterface;
116 /* This identifies the network interface on the local side to
117 use for the session. It is an ASCIIZ string in the form
118 that the Curl recognizes for setting its CURLOPT_INTERFACE
119 option (also the --interface option of the Curl program).
120 E.g. "9.1.72.189" or "giraffe-data.com" or "eth0".
122 It isn't necessarily valid, but it does have a terminating NUL.
124 NULL means we have no preference.
126 xmlrpc_bool sslVerifyPeer;
127 /* In an SSL connection, we should authenticate the server's SSL
128 certificate -- refuse to talk to him if it isn't authentic.
129 This is equivalent to Curl's CURLOPT_SSL_VERIFY_PEER option.
131 xmlrpc_bool sslVerifyHost;
132 /* In an SSL connection, we should verify that the server's
133 certificate (independently of whether the certificate is
134 authentic) indicates the host name that is in the URL we
135 are using for the server.
138 const char * sslCert;
139 const char * sslCertType;
140 const char * sslCertPasswd;
142 const char * sslKeyType;
143 const char * sslKeyPasswd;
144 const char * sslEngine;
145 bool sslEngineDefault;
146 unsigned int sslVersion;
149 const char * randomFile;
150 const char * egdSocket;
151 const char * sslCipherList;
155 /*============================================================================
157 ==============================================================================
158 This is the beginnings of a lock abstraction that will allow this
159 transport to be used with locks other than pthread locks
160 ============================================================================*/
163 pthread_mutex_t theLock;
164 void (*lock)(struct lock *);
165 void (*unlock)(struct lock *);
166 void (*destroy)(struct lock *);
169 typedef struct lock lock;
172 lock_pthread(struct lock * const lockP) {
173 pthread_mutex_lock(&lockP->theLock);
177 unlock_pthread(struct lock * const lockP) {
178 pthread_mutex_unlock(&lockP->theLock);
182 destroyLock_pthread(struct lock * const lockP) {
183 pthread_mutex_destroy(&lockP->theLock);
189 createLock_pthread(void) {
193 pthread_mutex_init(&lockP->theLock, NULL);
194 lockP->lock = &lock_pthread;
195 lockP->unlock = &unlock_pthread;
196 lockP->destroy = &destroyLock_pthread;
204 /*=============================================================================
206 =============================================================================*/
209 /*----------------------------------------------------------------------------
210 This is an extension to Curl's CURLM object. The extensions are:
212 1) It has a lock so multiple threads can use it simultaneously.
214 2) Its "select" file descriptor vectors are self-contained. CURLM
215 requires the user to maintain them separately.
216 -----------------------------------------------------------------------------*/
219 /* Hold this lock while accessing or using *curlMultiP. You're
220 using the multi manager whenever you're calling a Curl
221 library multi manager function.
223 /* The following file descriptor sets are an integral part of the
224 CURLM object; Our curlMulti_fdset() routine binds them to the
225 CURLM object, and said object expects us to use them in a very
226 specific way, including doing a select() on them. It is very,
236 static struct curlMulti *
237 createCurlMulti(void) {
239 struct curlMulti * retval;
240 struct curlMulti * curlMultiP;
242 MALLOCVAR(curlMultiP);
244 if (curlMultiP == NULL)
247 curlMultiP->lockP = createLock_pthread();
249 if (curlMultiP->lockP == NULL)
252 curlMultiP->curlMultiP = curl_multi_init();
253 if (curlMultiP->curlMultiP == NULL)
259 curlMultiP->lockP->destroy(curlMultiP->lockP);
270 destroyCurlMulti(struct curlMulti * const curlMultiP) {
272 curl_multi_cleanup(curlMultiP->curlMultiP);
274 curlMultiP->lockP->destroy(curlMultiP->lockP);
282 curlMulti_perform(xmlrpc_env * const envP,
283 struct curlMulti * const curlMultiP,
284 bool * const immediateWorkToDoP,
285 int * const runningHandlesP) {
289 curlMultiP->lockP->lock(curlMultiP->lockP);
291 rc = curl_multi_perform(curlMultiP->curlMultiP, runningHandlesP);
293 curlMultiP->lockP->unlock(curlMultiP->lockP);
295 if (rc == CURLM_CALL_MULTI_PERFORM) {
296 *immediateWorkToDoP = true;
298 *immediateWorkToDoP = false;
300 if (rc != CURLM_OK) {
302 "Impossible failure of curl_multi_perform() "
311 curlMulti_addHandle(xmlrpc_env * const envP,
312 struct curlMulti * const curlMultiP,
313 CURL * const curlSessionP) {
317 curlMultiP->lockP->lock(curlMultiP->lockP);
319 rc = curl_multi_add_handle(curlMultiP->curlMultiP, curlSessionP);
321 curlMultiP->lockP->unlock(curlMultiP->lockP);
324 xmlrpc_faultf(envP, "Could not add Curl session to the "
325 "curl multi manager. curl_multi_add_handle() "
326 "returns error code %d", rc);
331 curlMulti_removeHandle(struct curlMulti * const curlMultiP,
332 CURL * const curlSessionP) {
334 curlMultiP->lockP->lock(curlMultiP->lockP);
336 curl_multi_remove_handle(curlMultiP->curlMultiP, curlSessionP);
338 curlMultiP->lockP->unlock(curlMultiP->lockP);
344 curlMulti_getMessage(struct curlMulti * const curlMultiP,
345 bool * const endOfMessagesP,
346 CURLMsg * const curlMsgP) {
347 /*----------------------------------------------------------------------------
348 Get the next message from the queue of things the Curl multi manager
351 Return the message as *curlMsgP.
353 Iff there are no messages in the queue, return *endOfMessagesP == true.
354 -----------------------------------------------------------------------------*/
355 int remainingMsgCount;
356 CURLMsg * privateCurlMsgP;
357 /* Note that this is a pointer into the multi manager's memory,
358 so we have to use it under lock.
361 curlMultiP->lockP->lock(curlMultiP->lockP);
363 privateCurlMsgP = curl_multi_info_read(curlMultiP->curlMultiP,
366 if (privateCurlMsgP == NULL)
367 *endOfMessagesP = true;
369 *endOfMessagesP = false;
370 *curlMsgP = *privateCurlMsgP;
372 curlMultiP->lockP->unlock(curlMultiP->lockP);
378 curlMulti_fdset(xmlrpc_env * const envP,
379 struct curlMulti * const curlMultiP,
380 fd_set * const readFdSetP,
381 fd_set * const writeFdSetP,
382 fd_set * const exceptFdSetP,
383 int * const maxFdP) {
384 /*----------------------------------------------------------------------------
385 Set the CURLM object's file descriptor sets to those in the
386 curlMulti object, update those file descriptor sets with the
387 current needs of the multi manager, and return the resulting values
388 of the file descriptor sets.
390 This is a bizarre operation, but is necessary because of the nonmodular
391 way in which the Curl multi interface works with respect to waiting
392 for work with select().
393 -----------------------------------------------------------------------------*/
396 curlMultiP->lockP->lock(curlMultiP->lockP);
398 /* curl_multi_fdset() doesn't _set_ the fdsets. It adds to existing
399 ones (so you can easily do a select() on other fds and Curl
400 fds at the same time). So we have to clear first:
402 FD_ZERO(&curlMultiP->readFdSet);
403 FD_ZERO(&curlMultiP->writeFdSet);
404 FD_ZERO(&curlMultiP->exceptFdSet);
406 /* WARNING: curl_multi_fdset() doesn't just update the fdsets pointed
407 to by its arguments. It makes the CURLM object remember those
408 pointers and refer back to them later! In fact, curl_multi_perform
409 expects its caller to have done a select() on those masks. No,
410 really. The man page even admits it.
413 rc = curl_multi_fdset(curlMultiP->curlMultiP,
414 &curlMultiP->readFdSet,
415 &curlMultiP->writeFdSet,
416 &curlMultiP->exceptFdSet,
419 *readFdSetP = curlMultiP->readFdSet;
420 *writeFdSetP = curlMultiP->writeFdSet;
421 *exceptFdSetP = curlMultiP->exceptFdSet;
423 curlMultiP->lockP->unlock(curlMultiP->lockP);
426 xmlrpc_faultf(envP, "Impossible failure of curl_multi_fdset() "
433 curlMulti_updateFdSet(struct curlMulti * const curlMultiP,
434 fd_set const readFdSet,
435 fd_set const writeFdSet,
436 fd_set const exceptFdSet) {
437 /*----------------------------------------------------------------------------
438 curl_multi_perform() expects the file descriptor sets, which were bound
439 to the CURLM object via a prior curlMulti_fdset(), to contain the results
440 of a recent select(). This subroutine provides you a way to supply those.
441 -----------------------------------------------------------------------------*/
442 curlMultiP->readFdSet = readFdSet;
443 curlMultiP->writeFdSet = writeFdSet;
444 curlMultiP->exceptFdSet = exceptFdSet;
449 /*===========================================================================*/
452 struct xmlrpc_client_transport {
453 struct curlMulti * curlMultiP;
454 /* The Curl multi manager that this transport uses to handle
455 multiple Curl sessions at the same time.
457 CURL * syncCurlSessionP;
458 /* Handle for a Curl library session object that we use for
459 all synchronous RPCs. An async RPC has one of its own,
460 and consequently does not share things such as persistent
461 connections and cookies with any other RPC.
463 lock * syncCurlSessionLockP;
464 /* Hold this lock while accessing or using *syncCurlSessionP.
465 You're using the session from the time you set any
466 attributes in it or start a transaction with it until any
467 transaction has finished and you've lost interest in any
468 attributes of the session.
470 const char * userAgent;
471 /* Prefix for the User-Agent HTTP header, reflecting facilities
472 outside of Xmlrpc-c. The actual User-Agent header consists
473 of this prefix plus information about Xmlrpc-c. NULL means
478 struct curlSetup curlSetupStuff;
479 /* This is constant */
483 /* This is all stuff that really ought to be in a Curl object, but
484 the Curl library is a little too simple for that. So we build
485 a layer on top of Curl, and define this "transaction," as an
486 object subordinate to a Curl "session." A Curl session has
487 zero or one transactions in progress. The Curl session
488 "private data" is a pointer to the CurlTransaction object for
489 the current transaction.
492 /* Handle for the Curl session that hosts this transaction.
493 Note that only one transaction at a time can use a particular
494 Curl session, so this had better not be a session that some other
495 transaction is using simultaneously.
497 struct curlMulti * curlMultiP;
498 /* The Curl multi manager which manages the above curl session,
499 if any. An asynchronous process uses a Curl multi manager
500 to manage the in-progress Curl sessions and thereby in-progress
501 RPCs. A synchronous process has no need of a Curl multi manager.
504 /* The RPC which this transaction serves. (If this structure
505 were a true extension of the Curl library as described above,
506 this would be a void *, since the Curl library doesn't know what
507 an RPC is, but since we use it only for that, we might as well
508 use the specific type here).
510 char curlError[CURL_ERROR_SIZE];
511 /* Error message from Curl */
512 struct curl_slist * headerList;
513 /* The HTTP headers for the transaction */
514 const char * serverUrl; /* malloc'ed - belongs to this object */
520 curlTransaction * curlTransactionP;
521 /* The object which does the HTTP transaction, with no knowledge
522 of XML-RPC or Xmlrpc-c.
524 xmlrpc_mem_block * responseXmlP;
525 /* Where the response XML for this RPC should go or has gone. */
526 xmlrpc_transport_asynch_complete complete;
527 /* Routine to call to complete the RPC after it is complete HTTP-wise.
530 struct xmlrpc_call_info * callInfoP;
531 /* User's identifier for this RPC */
536 timeDiffMillisec(struct timeval const minuend,
537 struct timeval const subtractor) {
539 return (minuend.tv_sec - subtractor.tv_sec) * 1000 +
540 (minuend.tv_usec - subtractor.tv_usec + 500) / 1000;
546 timeIsAfter(struct timeval const comparator,
547 struct timeval const comparand) {
549 if (comparator.tv_sec > comparand.tv_sec)
551 else if (comparator.tv_sec < comparand.tv_sec)
554 /* Seconds are equal */
555 if (comparator.tv_usec > comparand.tv_usec)
565 addMilliseconds(struct timeval const addend,
566 unsigned int const adder,
567 struct timeval * const sumP) {
569 unsigned int newRawUsec;
571 newRawUsec = addend.tv_usec + adder * 1000;
573 sumP->tv_sec = addend.tv_sec + newRawUsec / 1000000;
574 sumP->tv_usec = newRawUsec % 1000000;
580 lockSyncCurlSession(struct xmlrpc_client_transport * const transportP) {
581 transportP->syncCurlSessionLockP->lock(transportP->syncCurlSessionLockP);
587 unlockSyncCurlSession(struct xmlrpc_client_transport * const transportP) {
588 transportP->syncCurlSessionLockP->unlock(transportP->syncCurlSessionLockP);
594 collect(void * const ptr,
597 FILE * const stream) {
598 /*----------------------------------------------------------------------------
599 This is a Curl output function. Curl calls this to deliver the
600 HTTP response body. Curl thinks it's writing to a POSIX stream.
601 -----------------------------------------------------------------------------*/
602 xmlrpc_mem_block * const responseXmlP = (xmlrpc_mem_block *) stream;
603 char * const buffer = ptr;
604 size_t const length = nmemb * size;
609 xmlrpc_env_init(&env);
610 xmlrpc_mem_block_append(&env, responseXmlP, buffer, length);
611 if (env.fault_occurred)
614 /* Really? Shouldn't it be like fread() and return 'nmemb'? */
623 initWindowsStuff(xmlrpc_env * const envP ATTR_UNUSED) {
626 /* This is CRITICAL so that cURL-Win32 works properly! */
628 /* So this commenter says, but I wonder why. libcurl should do the
629 required WSAStartup() itself, and it looks to me like it does.
632 WORD wVersionRequested;
635 wVersionRequested = MAKEWORD(1, 1);
637 err = WSAStartup(wVersionRequested, &wsaData);
639 xmlrpc_env_set_fault_formatted(
640 envP, XMLRPC_INTERNAL_ERROR,
641 "Winsock startup failed. WSAStartup returned rc %d", err);
643 if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
644 /* Tell the user that we couldn't find a useable */
646 xmlrpc_env_set_fault_formatted(
647 envP, XMLRPC_INTERNAL_ERROR, "Winsock reported that "
648 "it does not implement the requested version 1.1.");
650 if (envP->fault_occurred)
659 termWindowsStuff(void) {
669 getXportParms(xmlrpc_env * const envP ATTR_UNUSED,
670 const struct xmlrpc_curl_xportparms * const curlXportParmsP,
671 size_t const parmSize,
672 struct xmlrpc_client_transport * const transportP) {
673 /*----------------------------------------------------------------------------
674 Get the parameters out of *curlXportParmsP and update *transportP
677 *curlXportParmsP is a 'parmSize' bytes long prefix of
678 struct xmlrpc_curl_xportparms.
680 curlXportParmsP is something the user created. It's designed to be
681 friendly to the user, not to this program, and is encumbered by
682 lots of backward compatibility constraints. In particular, the
683 user may have coded and/or compiled it at a time that struct
684 xmlrpc_curl_xportparms was smaller than it is now!
686 So that's why we don't simply attach a copy of *curlXportParmsP to
689 To the extent that *curlXportParmsP is too small to contain a parameter,
690 we return the default value for that parameter.
692 Special case: curlXportParmsP == NULL means there is no input at all.
693 In that case, we return default values for everything.
694 -----------------------------------------------------------------------------*/
695 struct curlSetup * const curlSetupP = &transportP->curlSetupStuff;
697 if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(user_agent))
698 transportP->userAgent = NULL;
699 else if (curlXportParmsP->user_agent == NULL)
700 transportP->userAgent = NULL;
702 transportP->userAgent = strdup(curlXportParmsP->user_agent);
704 if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(network_interface))
705 curlSetupP->networkInterface = NULL;
706 else if (curlXportParmsP->network_interface == NULL)
707 curlSetupP->networkInterface = NULL;
709 curlSetupP->networkInterface =
710 strdup(curlXportParmsP->network_interface);
712 if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(no_ssl_verifypeer))
713 curlSetupP->sslVerifyPeer = true;
715 curlSetupP->sslVerifyPeer = !curlXportParmsP->no_ssl_verifypeer;
717 if (!curlXportParmsP ||
718 parmSize < XMLRPC_CXPSIZE(no_ssl_verifyhost))
719 curlSetupP->sslVerifyHost = true;
721 curlSetupP->sslVerifyHost = !curlXportParmsP->no_ssl_verifyhost;
723 if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(ssl_cert))
724 curlSetupP->sslCert = NULL;
725 else if (curlXportParmsP->ssl_cert == NULL)
726 curlSetupP->sslCert = NULL;
728 curlSetupP->sslCert = strdup(curlXportParmsP->ssl_cert);
730 if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslcerttype))
731 curlSetupP->sslCertType = NULL;
732 else if (curlXportParmsP->sslcerttype == NULL)
733 curlSetupP->sslCertType = NULL;
735 curlSetupP->sslCertType = strdup(curlXportParmsP->sslcerttype);
737 if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslcertpasswd))
738 curlSetupP->sslCertPasswd = NULL;
739 else if (curlXportParmsP->sslcertpasswd == NULL)
740 curlSetupP->sslCertPasswd = NULL;
742 curlSetupP->sslCertPasswd = strdup(curlXportParmsP->sslcertpasswd);
744 if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslkey))
745 curlSetupP->sslKey = NULL;
746 else if (curlXportParmsP->sslkey == NULL)
747 curlSetupP->sslKey = NULL;
749 curlSetupP->sslKey = strdup(curlXportParmsP->sslkey);
751 if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslkeytype))
752 curlSetupP->sslKeyType = NULL;
753 else if (curlXportParmsP->sslkeytype == NULL)
754 curlSetupP->sslKeyType = NULL;
756 curlSetupP->sslKeyType = strdup(curlXportParmsP->sslkeytype);
758 if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslkeypasswd))
759 curlSetupP->sslKeyPasswd = NULL;
760 else if (curlXportParmsP->sslkeypasswd == NULL)
761 curlSetupP->sslKeyPasswd = NULL;
763 curlSetupP->sslKeyPasswd = strdup(curlXportParmsP->sslkeypasswd);
765 if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslengine))
766 curlSetupP->sslEngine = NULL;
767 else if (curlXportParmsP->sslengine == NULL)
768 curlSetupP->sslEngine = NULL;
770 curlSetupP->sslEngine = strdup(curlXportParmsP->sslengine);
772 if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslengine_default))
773 curlSetupP->sslEngineDefault = false;
775 curlSetupP->sslEngineDefault = !!curlXportParmsP->sslengine_default;
777 if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(sslversion))
778 curlSetupP->sslVersion = XMLRPC_SSLVERSION_DEFAULT;
780 curlSetupP->sslVersion = curlXportParmsP->sslversion;
782 if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(cainfo))
783 curlSetupP->caInfo = NULL;
784 else if (curlXportParmsP->cainfo == NULL)
785 curlSetupP->caInfo = NULL;
787 curlSetupP->caInfo = strdup(curlXportParmsP->cainfo);
789 if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(capath))
790 curlSetupP->caPath = NULL;
791 else if (curlXportParmsP->capath == NULL)
792 curlSetupP->caPath = NULL;
794 curlSetupP->caPath = strdup(curlXportParmsP->capath);
796 if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(randomfile))
797 curlSetupP->randomFile = NULL;
798 else if (curlXportParmsP->randomfile == NULL)
799 curlSetupP->randomFile = NULL;
801 curlSetupP->randomFile = strdup(curlXportParmsP->randomfile);
803 if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(egdsocket))
804 curlSetupP->egdSocket = NULL;
805 else if (curlXportParmsP->egdsocket == NULL)
806 curlSetupP->egdSocket = NULL;
808 curlSetupP->egdSocket = strdup(curlXportParmsP->egdsocket);
810 if (!curlXportParmsP || parmSize < XMLRPC_CXPSIZE(ssl_cipher_list))
811 curlSetupP->sslCipherList = NULL;
812 else if (curlXportParmsP->ssl_cipher_list == NULL)
813 curlSetupP->sslCipherList = NULL;
815 curlSetupP->sslCipherList = strdup(curlXportParmsP->ssl_cipher_list);
822 freeXportParms(const struct xmlrpc_client_transport * const transportP) {
824 const struct curlSetup * const curlSetupP = &transportP->curlSetupStuff;
826 if (curlSetupP->sslCipherList)
827 xmlrpc_strfree(curlSetupP->sslCipherList);
828 if (curlSetupP->egdSocket)
829 xmlrpc_strfree(curlSetupP->egdSocket);
830 if (curlSetupP->randomFile)
831 xmlrpc_strfree(curlSetupP->randomFile);
832 if (curlSetupP->caPath)
833 xmlrpc_strfree(curlSetupP->caPath);
834 if (curlSetupP->caInfo)
835 xmlrpc_strfree(curlSetupP->caInfo);
836 if (curlSetupP->sslEngine)
837 xmlrpc_strfree(curlSetupP->sslEngine);
838 if (curlSetupP->sslKeyPasswd)
839 xmlrpc_strfree(curlSetupP->sslKeyPasswd);
840 if (curlSetupP->sslKeyType)
841 xmlrpc_strfree(curlSetupP->sslKeyType);
842 if (curlSetupP->sslKey)
843 xmlrpc_strfree(curlSetupP->sslKey);
844 if (curlSetupP->sslCertPasswd)
845 xmlrpc_strfree(curlSetupP->sslCertPasswd);
846 if (curlSetupP->sslCertType)
847 xmlrpc_strfree(curlSetupP->sslCertType);
848 if (curlSetupP->sslCert)
849 xmlrpc_strfree(curlSetupP->sslCert);
850 if (curlSetupP->networkInterface)
851 xmlrpc_strfree(curlSetupP->networkInterface);
852 if (transportP->userAgent)
853 xmlrpc_strfree(transportP->userAgent);
859 createSyncCurlSession(xmlrpc_env * const envP,
860 CURL ** const curlSessionPP) {
861 /*----------------------------------------------------------------------------
862 Create a Curl session to be used for multiple serial transactions.
863 The Curl session we create is not complete -- it still has to be
864 further set up for each particular transaction.
866 We can't set up anything here that changes from one transaction to the
869 We don't bother setting up anything that has to be set up for an
870 asynchronous transaction because code that is common between synchronous
871 and asynchronous transactions takes care of that anyway.
873 That leaves things, such as cookies, that don't exist for
874 asynchronous transactions, and are common to multiple serial
875 synchronous transactions.
876 -----------------------------------------------------------------------------*/
877 CURL * const curlSessionP = curl_easy_init();
879 if (curlSessionP == NULL)
880 xmlrpc_faultf(envP, "Could not create Curl session. "
881 "curl_easy_init() failed.");
883 /* The following is a trick. CURLOPT_COOKIEFILE is the name
884 of the file containing the initial cookies for the Curl
885 session. But setting it is also what turns on the cookie
886 function itself, whereby the Curl library accepts and
887 stores cookies from the server and sends them back on
888 future requests. We don't have a file of initial cookies, but
889 we want to turn on cookie function, so we set the option to
890 something we know does not validly name a file. Curl will
891 ignore the error and just start up cookie function with no
894 curl_easy_setopt(curlSessionP, CURLOPT_COOKIEFILE, "");
896 *curlSessionPP = curlSessionP;
903 destroySyncCurlSession(CURL * const curlSessionP) {
905 curl_easy_cleanup(curlSessionP);
911 makeSyncCurlSession(xmlrpc_env * const envP,
912 struct xmlrpc_client_transport * const transportP) {
914 transportP->syncCurlSessionLockP = createLock_pthread();
915 if (transportP->syncCurlSessionLockP == NULL)
916 xmlrpc_faultf(envP, "Unable to create lock for "
917 "synchronous Curl session.");
919 createSyncCurlSession(envP, &transportP->syncCurlSessionP);
920 if (envP->fault_occurred)
921 transportP->syncCurlSessionLockP->destroy(
922 transportP->syncCurlSessionLockP);
929 unmakeSyncCurlSession(struct xmlrpc_client_transport * const transportP) {
931 destroySyncCurlSession(transportP->syncCurlSessionP);
933 transportP->syncCurlSessionLockP->destroy(
934 transportP->syncCurlSessionLockP);
940 assertConstantsMatch(void) {
941 /*----------------------------------------------------------------------------
942 There are some constants that we define as part of the Xmlrpc-c
943 interface that are identical to constants in the Curl interface to
944 make curl option setting work. This function asserts such
946 -----------------------------------------------------------------------------*/
947 assert(XMLRPC_SSLVERSION_DEFAULT == CURL_SSLVERSION_DEFAULT);
948 assert(XMLRPC_SSLVERSION_TLSv1 == CURL_SSLVERSION_TLSv1);
949 assert(XMLRPC_SSLVERSION_SSLv2 == CURL_SSLVERSION_SSLv2);
950 assert(XMLRPC_SSLVERSION_SSLv3 == CURL_SSLVERSION_SSLv3);
956 create(xmlrpc_env * const envP,
957 int const flags ATTR_UNUSED,
958 const char * const appname ATTR_UNUSED,
959 const char * const appversion ATTR_UNUSED,
960 const struct xmlrpc_xportparms * const transportparmsP,
961 size_t const parm_size,
962 struct xmlrpc_client_transport ** const handlePP) {
963 /*----------------------------------------------------------------------------
964 This does the 'create' operation for a Curl client transport.
965 -----------------------------------------------------------------------------*/
966 struct xmlrpc_curl_xportparms * const curlXportParmsP =
967 (struct xmlrpc_curl_xportparms *) transportparmsP;
969 struct xmlrpc_client_transport * transportP;
971 assertConstantsMatch();
973 MALLOCVAR(transportP);
974 if (transportP == NULL)
975 xmlrpc_faultf(envP, "Unable to allocate transport descriptor.");
977 transportP->curlMultiP = createCurlMulti();
979 if (transportP->curlMultiP == NULL)
980 xmlrpc_faultf(envP, "Unable to create Curl multi manager");
982 getXportParms(envP, curlXportParmsP, parm_size, transportP);
984 if (!envP->fault_occurred) {
985 makeSyncCurlSession(envP, transportP);
987 if (envP->fault_occurred)
988 freeXportParms(transportP);
990 if (envP->fault_occurred)
991 destroyCurlMulti(transportP->curlMultiP);
993 if (envP->fault_occurred)
996 *handlePP = transportP;
1002 assertNoOutstandingCurlWork(struct curlMulti * const curlMultiP) {
1005 bool immediateWorkToDo;
1008 xmlrpc_env_init(&env);
1010 curlMulti_perform(&env, curlMultiP, &immediateWorkToDo, &runningHandles);
1012 /* We know the above was a no-op, since we're asserting that there
1013 is no outstanding work.
1015 XMLRPC_ASSERT(!env.fault_occurred);
1016 XMLRPC_ASSERT(!immediateWorkToDo);
1017 XMLRPC_ASSERT(runningHandles == 0);
1018 xmlrpc_env_clean(&env);
1024 destroy(struct xmlrpc_client_transport * const clientTransportP) {
1025 /*----------------------------------------------------------------------------
1026 This does the 'destroy' operation for a Curl client transport.
1027 -----------------------------------------------------------------------------*/
1028 XMLRPC_ASSERT(clientTransportP != NULL);
1030 assertNoOutstandingCurlWork(clientTransportP->curlMultiP);
1031 /* We know this is true because a condition of destroying the
1032 transport is that there be no outstanding RPCs.
1034 unmakeSyncCurlSession(clientTransportP);
1036 destroyCurlMulti(clientTransportP->curlMultiP);
1038 freeXportParms(clientTransportP);
1040 free(clientTransportP);
1046 addHeader(xmlrpc_env * const envP,
1047 struct curl_slist ** const headerListP,
1048 const char * const headerText) {
1050 struct curl_slist * newHeaderList;
1051 newHeaderList = curl_slist_append(*headerListP, headerText);
1052 if (newHeaderList == NULL)
1054 "Could not add header '%s'. "
1055 "curl_slist_append() failed.", headerText);
1057 *headerListP = newHeaderList;
1063 addContentTypeHeader(xmlrpc_env * const envP,
1064 struct curl_slist ** const headerListP) {
1066 addHeader(envP, headerListP, "Content-Type: text/xml");
1072 addUserAgentHeader(xmlrpc_env * const envP,
1073 struct curl_slist ** const headerListP,
1074 const char * const userAgent) {
1077 curl_version_info_data * const curlInfoP =
1078 curl_version_info(CURLVERSION_NOW);
1079 char curlVersion[32];
1080 const char * userAgentHeader;
1082 snprintf(curlVersion, sizeof(curlVersion), "%u.%u.%u",
1083 (curlInfoP->version_num >> 16) && 0xff,
1084 (curlInfoP->version_num >> 8) && 0xff,
1085 (curlInfoP->version_num >> 0) && 0xff
1088 xmlrpc_asprintf(&userAgentHeader,
1089 "User-Agent: %s Xmlrpc-c/%s Curl/%s",
1090 userAgent, XMLRPC_C_VERSION, curlVersion);
1092 if (userAgentHeader == xmlrpc_strsol)
1093 xmlrpc_faultf(envP, "Couldn't allocate memory for "
1094 "User-Agent header");
1096 addHeader(envP, headerListP, userAgentHeader);
1098 xmlrpc_strfree(userAgentHeader);
1106 addAuthorizationHeader(xmlrpc_env * const envP,
1107 struct curl_slist ** const headerListP,
1108 const char * const basicAuthInfo) {
1110 if (basicAuthInfo) {
1111 const char * authorizationHeader;
1113 xmlrpc_asprintf(&authorizationHeader, "Authorization: %s",
1116 if (authorizationHeader == xmlrpc_strsol)
1117 xmlrpc_faultf(envP, "Couldn't allocate memory for "
1118 "Authorization header");
1120 addHeader(envP, headerListP, authorizationHeader);
1122 xmlrpc_strfree(authorizationHeader);
1130 createCurlHeaderList(xmlrpc_env * const envP,
1131 const xmlrpc_server_info * const serverP,
1132 const char * const userAgent,
1133 struct curl_slist ** const headerListP) {
1135 struct curl_slist * headerList;
1137 headerList = NULL; /* initial value - empty list */
1139 addContentTypeHeader(envP, &headerList);
1140 if (!envP->fault_occurred) {
1141 addUserAgentHeader(envP, &headerList, userAgent);
1142 if (!envP->fault_occurred) {
1143 addAuthorizationHeader(envP, &headerList,
1144 serverP->_http_basic_auth);
1147 if (envP->fault_occurred)
1148 curl_slist_free_all(headerList);
1150 *headerListP = headerList;
1156 setupCurlSession(xmlrpc_env * const envP,
1157 curlTransaction * const curlTransactionP,
1158 xmlrpc_mem_block * const callXmlP,
1159 xmlrpc_mem_block * const responseXmlP,
1160 const struct curlSetup * const curlSetupP) {
1161 /*----------------------------------------------------------------------------
1162 Set up the Curl session for the transaction *curlTransactionP so that
1163 a subsequent curl_easy_perform() will perform said transaction.
1164 -----------------------------------------------------------------------------*/
1165 CURL * const curlSessionP = curlTransactionP->curlSessionP;
1167 assertConstantsMatch();
1169 curl_easy_setopt(curlSessionP, CURLOPT_POST, 1);
1170 curl_easy_setopt(curlSessionP, CURLOPT_URL, curlTransactionP->serverUrl);
1172 XMLRPC_MEMBLOCK_APPEND(char, envP, callXmlP, "\0", 1);
1173 if (!envP->fault_occurred) {
1174 curl_easy_setopt(curlSessionP, CURLOPT_POSTFIELDS,
1175 XMLRPC_MEMBLOCK_CONTENTS(char, callXmlP));
1177 curl_easy_setopt(curlSessionP, CURLOPT_WRITEFUNCTION, collect);
1178 curl_easy_setopt(curlSessionP, CURLOPT_FILE, responseXmlP);
1179 curl_easy_setopt(curlSessionP, CURLOPT_HEADER, 0);
1180 curl_easy_setopt(curlSessionP, CURLOPT_ERRORBUFFER,
1181 curlTransactionP->curlError);
1182 curl_easy_setopt(curlSessionP, CURLOPT_NOPROGRESS, 1);
1184 curl_easy_setopt(curlSessionP, CURLOPT_HTTPHEADER,
1185 curlTransactionP->headerList);
1187 curl_easy_setopt(curlSessionP, CURLOPT_SSL_VERIFYPEER,
1188 curlSetupP->sslVerifyPeer);
1189 curl_easy_setopt(curlSessionP, CURLOPT_SSL_VERIFYHOST,
1190 curlSetupP->sslVerifyHost ? 2 : 0);
1192 if (curlSetupP->networkInterface)
1193 curl_easy_setopt(curlSessionP, CURLOPT_INTERFACE,
1194 curlSetupP->networkInterface);
1195 if (curlSetupP->sslCert)
1196 curl_easy_setopt(curlSessionP, CURLOPT_SSLCERT,
1197 curlSetupP->sslCert);
1198 if (curlSetupP->sslCertType)
1199 curl_easy_setopt(curlSessionP, CURLOPT_SSLCERTTYPE,
1200 curlSetupP->sslCertType);
1201 if (curlSetupP->sslCertPasswd)
1202 curl_easy_setopt(curlSessionP, CURLOPT_SSLCERTPASSWD,
1203 curlSetupP->sslCertPasswd);
1204 if (curlSetupP->sslKey)
1205 curl_easy_setopt(curlSessionP, CURLOPT_SSLKEY,
1206 curlSetupP->sslKey);
1207 if (curlSetupP->sslKeyType)
1208 curl_easy_setopt(curlSessionP, CURLOPT_SSLKEYTYPE,
1209 curlSetupP->sslKeyType);
1210 if (curlSetupP->sslKeyPasswd)
1211 curl_easy_setopt(curlSessionP, CURLOPT_SSLKEYPASSWD,
1212 curlSetupP->sslKeyPasswd);
1213 if (curlSetupP->sslEngine)
1214 curl_easy_setopt(curlSessionP, CURLOPT_SSLENGINE,
1215 curlSetupP->sslEngine);
1216 if (curlSetupP->sslEngineDefault)
1217 /* 3rd argument seems to be required by some Curl */
1218 curl_easy_setopt(curlSessionP, CURLOPT_SSLENGINE_DEFAULT, 1l);
1219 if (curlSetupP->sslVersion != XMLRPC_SSLVERSION_DEFAULT)
1220 curl_easy_setopt(curlSessionP, CURLOPT_SSLVERSION,
1221 curlSetupP->sslVersion);
1222 if (curlSetupP->caInfo)
1223 curl_easy_setopt(curlSessionP, CURLOPT_CAINFO,
1224 curlSetupP->caInfo);
1225 if (curlSetupP->caPath)
1226 curl_easy_setopt(curlSessionP, CURLOPT_CAPATH,
1227 curlSetupP->caPath);
1228 if (curlSetupP->randomFile)
1229 curl_easy_setopt(curlSessionP, CURLOPT_RANDOM_FILE,
1230 curlSetupP->randomFile);
1231 if (curlSetupP->egdSocket)
1232 curl_easy_setopt(curlSessionP, CURLOPT_EGDSOCKET,
1233 curlSetupP->egdSocket);
1234 if (curlSetupP->sslCipherList)
1235 curl_easy_setopt(curlSessionP, CURLOPT_SSL_CIPHER_LIST,
1236 curlSetupP->sslCipherList);
1243 createCurlTransaction(xmlrpc_env * const envP,
1244 CURL * const curlSessionP,
1245 struct curlMulti * const curlMultiP,
1246 const xmlrpc_server_info * const serverP,
1247 xmlrpc_mem_block * const callXmlP,
1248 xmlrpc_mem_block * const responseXmlP,
1249 const char * const userAgent,
1250 const struct curlSetup * const curlSetupStuffP,
1252 curlTransaction ** const curlTransactionPP) {
1254 curlTransaction * curlTransactionP;
1256 MALLOCVAR(curlTransactionP);
1257 if (curlTransactionP == NULL)
1258 xmlrpc_faultf(envP, "No memory to create Curl transaction.");
1260 curlTransactionP->curlSessionP = curlSessionP;
1261 curlTransactionP->curlMultiP = curlMultiP;
1262 curlTransactionP->rpcP = rpcP;
1264 curlTransactionP->serverUrl = strdup(serverP->_server_url);
1265 if (curlTransactionP->serverUrl == NULL)
1266 xmlrpc_faultf(envP, "Out of memory to store server URL.");
1268 createCurlHeaderList(envP, serverP, userAgent,
1269 &curlTransactionP->headerList);
1271 if (!envP->fault_occurred)
1272 setupCurlSession(envP, curlTransactionP,
1273 callXmlP, responseXmlP,
1276 if (envP->fault_occurred)
1277 xmlrpc_strfree(curlTransactionP->serverUrl);
1279 if (envP->fault_occurred)
1280 free(curlTransactionP);
1282 *curlTransactionPP = curlTransactionP;
1288 destroyCurlTransaction(curlTransaction * const curlTransactionP) {
1290 curl_slist_free_all(curlTransactionP->headerList);
1291 xmlrpc_strfree(curlTransactionP->serverUrl);
1293 free(curlTransactionP);
1299 getCurlTransactionError(curlTransaction * const curlTransactionP,
1300 xmlrpc_env * const envP) {
1305 res = curl_easy_getinfo(curlTransactionP->curlSessionP,
1306 CURLINFO_HTTP_CODE, &http_result);
1308 if (res != CURLE_OK)
1309 xmlrpc_env_set_fault_formatted(
1310 envP, XMLRPC_INTERNAL_ERROR,
1311 "Curl performed the HTTP POST request, but was "
1312 "unable to say what the HTTP result code was. "
1313 "curl_easy_getinfo(CURLINFO_HTTP_CODE) says: %s",
1314 curlTransactionP->curlError);
1316 if (http_result != 200)
1317 xmlrpc_env_set_fault_formatted(
1318 envP, XMLRPC_NETWORK_ERROR, "HTTP response: %ld",
1326 performCurlTransaction(xmlrpc_env * const envP,
1327 curlTransaction * const curlTransactionP) {
1329 CURL * const curlSessionP = curlTransactionP->curlSessionP;
1333 res = curl_easy_perform(curlSessionP);
1335 if (res != CURLE_OK)
1336 xmlrpc_env_set_fault_formatted(
1337 envP, XMLRPC_NETWORK_ERROR, "Curl failed to perform "
1338 "HTTP POST request. curl_easy_perform() says: %s",
1339 curlTransactionP->curlError);
1341 getCurlTransactionError(curlTransactionP, envP);
1347 createRpc(xmlrpc_env * const envP,
1348 struct xmlrpc_client_transport * const clientTransportP,
1349 CURL * const curlSessionP,
1350 const xmlrpc_server_info * const serverP,
1351 xmlrpc_mem_block * const callXmlP,
1352 xmlrpc_mem_block * const responseXmlP,
1353 xmlrpc_transport_asynch_complete complete,
1354 struct xmlrpc_call_info * const callInfoP,
1355 rpc ** const rpcPP) {
1361 xmlrpc_faultf(envP, "Couldn't allocate memory for rpc object");
1363 rpcP->callInfoP = callInfoP;
1364 rpcP->complete = complete;
1365 rpcP->responseXmlP = responseXmlP;
1367 createCurlTransaction(envP,
1369 clientTransportP->curlMultiP,
1371 callXmlP, responseXmlP,
1372 clientTransportP->userAgent,
1373 &clientTransportP->curlSetupStuff,
1375 &rpcP->curlTransactionP);
1376 if (!envP->fault_occurred) {
1377 if (envP->fault_occurred)
1378 destroyCurlTransaction(rpcP->curlTransactionP);
1380 if (envP->fault_occurred)
1389 destroyRpc(rpc * const rpcP) {
1391 XMLRPC_ASSERT_PTR_OK(rpcP);
1393 destroyCurlTransaction(rpcP->curlTransactionP);
1401 performRpc(xmlrpc_env * const envP,
1404 performCurlTransaction(envP, rpcP->curlTransactionP);
1410 startCurlTransaction(xmlrpc_env * const envP,
1411 curlTransaction * const curlTransactionP) {
1413 /* A Curl session is serial -- it processes zero or one transaction
1414 at a time. We use the "private" attribute of the Curl session to
1415 indicate which transaction it is presently processing. This is
1416 important when the transaction finishes, because libcurl will just
1417 tell us that something finished on a particular session, not that
1418 a particular transaction finished.
1420 curl_easy_setopt(curlTransactionP->curlSessionP, CURLOPT_PRIVATE,
1423 curlMulti_addHandle(envP,
1424 curlTransactionP->curlMultiP,
1425 curlTransactionP->curlSessionP);
1431 startRpc(xmlrpc_env * const envP,
1434 startCurlTransaction(envP, rpcP->curlTransactionP);
1440 sendRequest(xmlrpc_env * const envP,
1441 struct xmlrpc_client_transport * const clientTransportP,
1442 const xmlrpc_server_info * const serverP,
1443 xmlrpc_mem_block * const callXmlP,
1444 xmlrpc_transport_asynch_complete complete,
1445 struct xmlrpc_call_info * const callInfoP) {
1446 /*----------------------------------------------------------------------------
1447 Initiate an XML-RPC rpc asynchronously. Don't wait for it to go to
1450 Unless we return failure, we arrange to have complete() called when
1453 This does the 'send_request' operation for a Curl client transport.
1454 -----------------------------------------------------------------------------*/
1456 xmlrpc_mem_block * responseXmlP;
1458 responseXmlP = XMLRPC_MEMBLOCK_NEW(char, envP, 0);
1459 if (!envP->fault_occurred) {
1460 CURL * const curlSessionP = curl_easy_init();
1462 if (curlSessionP == NULL)
1463 xmlrpc_faultf(envP, "Could not create Curl session. "
1464 "curl_easy_init() failed.");
1466 createRpc(envP, clientTransportP, curlSessionP, serverP,
1467 callXmlP, responseXmlP, complete, callInfoP,
1470 if (!envP->fault_occurred) {
1471 startRpc(envP, rpcP);
1473 if (envP->fault_occurred)
1476 if (envP->fault_occurred)
1477 curl_easy_cleanup(curlSessionP);
1479 if (envP->fault_occurred)
1480 XMLRPC_MEMBLOCK_FREE(char, responseXmlP);
1482 /* If we're returning success, the user's eventual finish_asynch
1483 call will destroy this RPC, Curl session, and response buffer
1484 and remove the Curl session from the Curl multi manager.
1485 (If we're returning failure, we didn't create any of those).
1492 finishCurlTransaction(xmlrpc_env * const envP ATTR_UNUSED,
1493 CURL * const curlSessionP,
1494 CURLcode const result) {
1495 /*----------------------------------------------------------------------------
1496 Handle the event that a Curl transaction has completed on the Curl
1497 session identified by 'curlSessionP'.
1499 Tell the requester of the RPC which this transaction serves the
1502 Remove the Curl session from its Curl multi manager and destroy the
1503 Curl session, the XML response buffer, the Curl transaction, and the RPC.
1504 -----------------------------------------------------------------------------*/
1505 curlTransaction * curlTransactionP;
1508 curl_easy_getinfo(curlSessionP, CURLINFO_PRIVATE, &curlTransactionP);
1510 rpcP = curlTransactionP->rpcP;
1512 curlMulti_removeHandle(curlTransactionP->curlMultiP,
1513 curlTransactionP->curlSessionP);
1517 xmlrpc_env_init(&env);
1519 if (result != CURLE_OK) {
1520 xmlrpc_env_set_fault_formatted(
1521 envP, XMLRPC_NETWORK_ERROR, "libcurl failed to execute the "
1522 "HTTP POST transaction. %s", curlTransactionP->curlError);
1524 getCurlTransactionError(curlTransactionP, &env);
1526 rpcP->complete(rpcP->callInfoP, rpcP->responseXmlP, env);
1528 xmlrpc_env_clean(&env);
1531 curl_easy_cleanup(curlSessionP);
1533 XMLRPC_MEMBLOCK_FREE(char, rpcP->responseXmlP);
1540 static struct timeval
1541 selectTimeout(xmlrpc_timeoutType const timeoutType,
1542 struct timeval const timeoutTime) {
1543 /*----------------------------------------------------------------------------
1544 Return the value that should be used in the select() call to wait for
1545 there to be work for the Curl multi manager to do, given that the user
1546 wants to timeout according to 'timeoutType' and 'timeoutTime'.
1547 -----------------------------------------------------------------------------*/
1548 unsigned int selectTimeoutMillisec;
1549 struct timeval retval;
1551 selectTimeoutMillisec = 0; // quiet compiler warning
1553 /* We assume there is work to do at least every 3 seconds, because
1554 the Curl multi manager often has retries and other scheduled work
1555 that doesn't involve file handles on which we can select().
1557 switch (timeoutType) {
1559 selectTimeoutMillisec = 3000;
1562 struct timeval nowTime;
1565 gettimeofday(&nowTime, NULL);
1566 timeLeft = timeDiffMillisec(timeoutTime, nowTime);
1568 selectTimeoutMillisec = MIN(3000, MAX(0, timeLeft));
1572 retval.tv_sec = selectTimeoutMillisec / 1000;
1573 retval.tv_usec = (selectTimeoutMillisec % 1000) * 1000;
1581 processCurlMessages(xmlrpc_env * const envP,
1582 struct curlMulti * const curlMultiP) {
1586 endOfMessages = false; /* initial assumption */
1588 while (!endOfMessages && !envP->fault_occurred) {
1591 curlMulti_getMessage(curlMultiP, &endOfMessages, &curlMsg);
1593 if (!endOfMessages) {
1594 if (curlMsg.msg == CURLMSG_DONE)
1595 finishCurlTransaction(envP, curlMsg.easy_handle,
1596 curlMsg.data.result);
1604 waitForWork(xmlrpc_env * const envP,
1605 struct curlMulti * const curlMultiP,
1606 xmlrpc_timeoutType const timeoutType,
1607 struct timeval const deadline) {
1614 curlMulti_fdset(envP, curlMultiP,
1615 &readFdSet, &writeFdSet, &exceptFdSet, &maxFd);
1616 if (!envP->fault_occurred) {
1618 /* There are no Curl file descriptors on which to wait.
1619 So either there's work to do right now or all transactions
1620 are already complete.
1623 struct timeval selectTimeoutArg;
1626 selectTimeoutArg = selectTimeout(timeoutType, deadline);
1628 rc = select(maxFd+1, &readFdSet, &writeFdSet, &exceptFdSet,
1632 xmlrpc_faultf(envP, "Impossible failure of select() "
1633 "with errno %d (%s)",
1634 errno, strerror(errno));
1636 /* Believe it or not, the Curl multi manager needs the
1637 results of our select(). So hand them over:
1639 curlMulti_updateFdSet(curlMultiP,
1640 readFdSet, writeFdSet, exceptFdSet);
1649 doCurlWork(xmlrpc_env * const envP,
1650 struct curlMulti * const curlMultiP,
1651 bool * const rpcStillRunningP) {
1652 /*----------------------------------------------------------------------------
1653 Do whatever work is ready to be done by the Curl multi manager
1654 identified by 'curlMultiP'. This typically is transferring data on
1655 an HTTP connection because the server is ready.
1657 Return *rpcStillRunningP false if this work completes all of the
1658 manager's transactions so that there is no reason to call us ever
1661 Where the multi manager completes an HTTP transaction, also complete
1663 -----------------------------------------------------------------------------*/
1664 bool immediateWorkToDo;
1667 immediateWorkToDo = true; /* initial assumption */
1669 while (immediateWorkToDo && !envP->fault_occurred) {
1670 curlMulti_perform(envP, curlMultiP,
1671 &immediateWorkToDo, &runningHandles);
1674 /* We either did all the work that's ready to do or hit an error. */
1676 if (!envP->fault_occurred) {
1677 /* The work we did may have resulted in asynchronous messages
1678 (asynchronous to the thing the refer to, not to us, of course).
1679 In particular the message "Curl transaction has completed".
1680 So we process those now.
1682 processCurlMessages(envP, curlMultiP);
1684 *rpcStillRunningP = runningHandles > 0;
1691 finishCurlSessions(xmlrpc_env * const envP,
1692 struct curlMulti * const curlMultiP,
1693 xmlrpc_timeoutType const timeoutType,
1694 struct timeval const deadline) {
1696 bool rpcStillRunning;
1699 rpcStillRunning = true; /* initial assumption */
1702 while (rpcStillRunning && !timedOut && !envP->fault_occurred) {
1703 waitForWork(envP, curlMultiP, timeoutType, deadline);
1705 if (!envP->fault_occurred) {
1706 struct timeval nowTime;
1708 doCurlWork(envP, curlMultiP, &rpcStillRunning);
1710 gettimeofday(&nowTime, NULL);
1712 timedOut = (timeoutType == timeout_yes &&
1713 timeIsAfter(nowTime, deadline));
1722 struct xmlrpc_client_transport * const clientTransportP,
1723 xmlrpc_timeoutType const timeoutType,
1724 xmlrpc_timeout const timeout) {
1725 /*----------------------------------------------------------------------------
1726 Wait for the Curl multi manager to finish the Curl transactions for
1727 all outstanding RPCs and destroy those RPCs.
1729 This does the 'finish_asynch' operation for a Curl client transport.
1731 It would be cool to replace this with something analogous to the
1732 Curl asynchronous interface: Have something like curl_multi_fdset()
1733 that returns a bunch of file descriptors on which the user can wait
1734 (along with possibly other file descriptors of his own) and
1735 something like curl_multi_perform() to finish whatever RPCs are
1736 ready to finish at that moment. The implementation would be little
1737 more than wrapping curl_multi_fdset() and curl_multi_perform().
1738 -----------------------------------------------------------------------------*/
1741 struct timeval waitTimeoutTime;
1742 /* The datetime after which we should quit waiting */
1744 xmlrpc_env_init(&env);
1746 if (timeoutType == timeout_yes) {
1747 struct timeval waitStartTime;
1748 gettimeofday(&waitStartTime, NULL);
1749 addMilliseconds(waitStartTime, timeout, &waitTimeoutTime);
1752 finishCurlSessions(&env, clientTransportP->curlMultiP,
1753 timeoutType, waitTimeoutTime);
1755 /* If the above fails, it is catastrophic, because it means there is
1756 no way to complete outstanding Curl transactions and RPCs, and
1757 no way to release their resources.
1759 We should at least expand this interface some day to push the
1760 problem back up the user, but for now we just do this Hail Mary
1763 Note that a failure of finishCurlSessions() does not mean that
1764 a session completed with an error or an RPC completed with an
1765 error. Those things are reported up through the user's
1766 xmlrpc_transport_asynch_complete routine. A failure here is
1767 something that stopped us from calling that.
1770 if (env.fault_occurred)
1771 fprintf(stderr, "finishAsync() failed. Xmlrpc-c Curl transport "
1772 "is now in an unknown state and may not be able to "
1773 "continue functioning. Specifics of the failure: %s\n",
1776 xmlrpc_env_clean(&env);
1782 call(xmlrpc_env * const envP,
1783 struct xmlrpc_client_transport * const clientTransportP,
1784 const xmlrpc_server_info * const serverP,
1785 xmlrpc_mem_block * const callXmlP,
1786 xmlrpc_mem_block ** const responseXmlPP) {
1788 xmlrpc_mem_block * responseXmlP;
1791 XMLRPC_ASSERT_ENV_OK(envP);
1792 XMLRPC_ASSERT_PTR_OK(serverP);
1793 XMLRPC_ASSERT_PTR_OK(callXmlP);
1794 XMLRPC_ASSERT_PTR_OK(responseXmlPP);
1796 responseXmlP = XMLRPC_MEMBLOCK_NEW(char, envP, 0);
1797 if (!envP->fault_occurred) {
1798 /* Only one RPC at a time can use a Curl session, so we have to
1799 hold the lock as long as our RPC exists.
1801 lockSyncCurlSession(clientTransportP);
1802 createRpc(envP, clientTransportP, clientTransportP->syncCurlSessionP,
1804 callXmlP, responseXmlP,
1808 if (!envP->fault_occurred) {
1809 performRpc(envP, rpcP);
1811 *responseXmlPP = responseXmlP;
1815 unlockSyncCurlSession(clientTransportP);
1816 if (envP->fault_occurred)
1817 XMLRPC_MEMBLOCK_FREE(char, responseXmlP);
1824 setupGlobalConstants(xmlrpc_env * const envP) {
1825 /*----------------------------------------------------------------------------
1826 See longwinded discussion of the global constant issue at the top of
1828 -----------------------------------------------------------------------------*/
1829 initWindowsStuff(envP);
1831 if (!envP->fault_occurred) {
1834 rc = curl_global_init(CURL_GLOBAL_ALL);
1837 xmlrpc_faultf(envP, "curl_global_init() failed with code %d", rc);
1844 teardownGlobalConstants(void) {
1845 /*----------------------------------------------------------------------------
1846 See longwinded discussionof the global constant issue at the top of
1848 -----------------------------------------------------------------------------*/
1849 curl_global_cleanup();
1856 struct xmlrpc_client_transport_ops xmlrpc_curl_transport_ops = {
1857 &setupGlobalConstants,
1858 &teardownGlobalConstants,