xmlrpc_curl_transport.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654
  1. /*=============================================================================
  2. xmlrpc_curl_transport
  3. ===============================================================================
  4. Curl-based client transport for Xmlrpc-c
  5. By Bryan Henderson 04.12.10.
  6. Contributed to the public domain by its author.
  7. =============================================================================*/
  8. #include "xmlrpc_config.h"
  9. #include "bool.h"
  10. #include "mallocvar.h"
  11. #include "linklist.h"
  12. #include "casprintf.h"
  13. #include "xmlrpc.h"
  14. #include "xmlrpc_int.h"
  15. #include "xmlrpc_client.h"
  16. #include "xmlrpc_client_int.h"
  17. #include <string.h>
  18. #include <stdlib.h>
  19. #include <errno.h>
  20. #include "xmlrpc_pthreads.h"
  21. #include <curl/curl.h>
  22. #include <curl/types.h>
  23. #include <curl/easy.h>
  24. #ifndef WIN32
  25. # include <unistd.h>
  26. #endif
  27. #if defined (WIN32) && defined(_DEBUG)
  28. # include <crtdbg.h>
  29. # define new DEBUG_NEW
  30. # define malloc(size) _malloc_dbg( size, _NORMAL_BLOCK, __FILE__, __LINE__)
  31. # undef THIS_FILE
  32. static char THIS_FILE[] = __FILE__;
  33. #endif /*WIN32 && _DEBUG*/
  34. struct clientTransport {
  35. pthread_mutex_t listLock;
  36. struct list_head rpcList;
  37. /* List of all RPCs that exist for this transport. An RPC exists
  38. from the time the user requests it until the time the user
  39. acknowledges it is done.
  40. */
  41. };
  42. typedef struct {
  43. /* This is all stuff that really ought to be in the CURL object,
  44. but the Curl library is a little too simple for that. So we
  45. build a layer on top of it, and call it a "transaction," as
  46. distinct from the Curl "session" represented by the CURL object.
  47. */
  48. CURL * curlSessionP;
  49. /* Handle for Curl library session object */
  50. char curlError[CURL_ERROR_SIZE];
  51. /* Error message from Curl */
  52. struct curl_slist * headerList;
  53. /* The HTTP headers for the transaction */
  54. const char * serverUrl; /* malloc'ed - belongs to this object */
  55. } curlTransaction;
  56. typedef struct {
  57. struct list_head link; /* link in transport's list of RPCs */
  58. curlTransaction * curlTransactionP;
  59. /* The object which does the HTTP transaction, with no knowledge
  60. of XML-RPC or Xmlrpc-c.
  61. */
  62. xmlrpc_mem_block * responseXmlP;
  63. xmlrpc_bool threadExists;
  64. pthread_t thread;
  65. transport_asynch_complete complete;
  66. /* Routine to call to complete the RPC after it is complete HTTP-wise.
  67. NULL if none.
  68. */
  69. struct call_info * callInfoP;
  70. /* User's identifier for this RPC */
  71. } rpc;
  72. static size_t
  73. collect(void * const ptr,
  74. size_t const size,
  75. size_t const nmemb,
  76. FILE * const stream) {
  77. /*----------------------------------------------------------------------------
  78. This is a Curl output function. Curl calls this to deliver the
  79. HTTP response body. Curl thinks it's writing to a POSIX stream.
  80. -----------------------------------------------------------------------------*/
  81. xmlrpc_mem_block * const responseXmlP = (xmlrpc_mem_block *) stream;
  82. char * const buffer = ptr;
  83. size_t const length = nmemb * size;
  84. size_t retval;
  85. xmlrpc_env env;
  86. xmlrpc_env_init(&env);
  87. xmlrpc_mem_block_append(&env, responseXmlP, buffer, length);
  88. if (env.fault_occurred)
  89. retval = (size_t)-1;
  90. else
  91. /* Really? Shouldn't it be like fread() and return 'nmemb'? */
  92. retval = length;
  93. return retval;
  94. }
  95. static void
  96. initWindowsStuff(xmlrpc_env * const envP) {
  97. #if defined (WIN32)
  98. /* This is CRITICAL so that cURL-Win32 works properly! */
  99. WORD wVersionRequested;
  100. WSADATA wsaData;
  101. int err;
  102. wVersionRequested = MAKEWORD(1, 1);
  103. err = WSAStartup(wVersionRequested, &wsaData);
  104. if (LOBYTE(wsaData.wVersion) != 1 ||
  105. HIBYTE( wsaData.wVersion) != 1) {
  106. /* Tell the user that we couldn't find a useable */
  107. /* winsock.dll. */
  108. WSACleanup();
  109. xmlrpc_env_set_fault_formatted(
  110. envP, XMLRPC_INTERNAL_ERROR, "Winsock reported that "
  111. "it does not implement the requested version 1.1.");
  112. }
  113. #else
  114. if (0)
  115. envP->fault_occurred = TRUE; /* Avoid unused parm warning */
  116. #endif
  117. }
  118. static void
  119. create(xmlrpc_env * const envP,
  120. int const flags ATTR_UNUSED,
  121. const char * const appname ATTR_UNUSED,
  122. const char * const appversion ATTR_UNUSED,
  123. struct clientTransport ** const handlePP) {
  124. /*----------------------------------------------------------------------------
  125. This does the 'create' operation for a Curl client transport.
  126. -----------------------------------------------------------------------------*/
  127. struct clientTransport * transportP;
  128. initWindowsStuff(envP);
  129. MALLOCVAR(transportP);
  130. if (transportP == NULL)
  131. xmlrpc_env_set_fault_formatted(
  132. envP, XMLRPC_INTERNAL_ERROR,
  133. "Unable to allocate transport descriptor.");
  134. else {
  135. pthread_mutex_init(&transportP->listLock, NULL);
  136. list_make_empty(&transportP->rpcList);
  137. /*
  138. * This is the main global constructor for the app. Call this before
  139. * _any_ libcurl usage. If this fails, *NO* libcurl functions may be
  140. * used, or havoc may be the result.
  141. */
  142. curl_global_init(CURL_GLOBAL_ALL);
  143. /* The above makes it look like Curl is not re-entrant. We should
  144. check into that.
  145. */
  146. *handlePP = transportP;
  147. }
  148. }
  149. static void
  150. termWindowStuff(void) {
  151. #if defined (WIN32)
  152. WSACleanup();
  153. #endif
  154. }
  155. static void
  156. destroy(struct clientTransport * const clientTransportP) {
  157. /*----------------------------------------------------------------------------
  158. This does the 'destroy' operation for a Libwww client transport.
  159. -----------------------------------------------------------------------------*/
  160. XMLRPC_ASSERT(clientTransportP != NULL);
  161. XMLRPC_ASSERT(list_is_empty(&clientTransportP->rpcList));
  162. pthread_mutex_destroy(&clientTransportP->listLock);
  163. curl_global_cleanup();
  164. termWindowStuff();
  165. free(clientTransportP);
  166. }
  167. static void
  168. createCurlHeaderList(xmlrpc_env * const envP,
  169. xmlrpc_server_info * const serverP,
  170. struct curl_slist ** const headerListP) {
  171. struct curl_slist * headerList;
  172. headerList = NULL; /* initial value */
  173. headerList = curl_slist_append(headerList, "Content-Type: text/xml");
  174. if (headerList == NULL)
  175. xmlrpc_env_set_fault_formatted(
  176. envP, XMLRPC_INTERNAL_ERROR,
  177. "Could not add header. curl_slist_append() failed.");
  178. else {
  179. /* Send an authorization header if we need one. */
  180. if (serverP->_http_basic_auth) {
  181. /* Make the authentication header "Authorization: " */
  182. /* we need 15 + length of _http_basic_auth + 1 for null */
  183. char * const authHeader =
  184. malloc(strlen(serverP->_http_basic_auth) + 15 + 1);
  185. if (authHeader == NULL)
  186. xmlrpc_env_set_fault_formatted(
  187. envP, XMLRPC_INTERNAL_ERROR,
  188. "Couldn't allocate memory for authentication header");
  189. else {
  190. memcpy(authHeader,"Authorization: ", 15);
  191. memcpy(authHeader + 15, serverP->_http_basic_auth,
  192. strlen(serverP->_http_basic_auth) + 1);
  193. headerList = curl_slist_append(headerList, authHeader);
  194. if (headerList == NULL)
  195. xmlrpc_env_set_fault_formatted(
  196. envP, XMLRPC_INTERNAL_ERROR,
  197. "Could not add authentication header. "
  198. "curl_slist_append() failed.");
  199. free(authHeader);
  200. }
  201. }
  202. if (envP->fault_occurred)
  203. free(headerList);
  204. }
  205. *headerListP = headerList;
  206. }
  207. static void
  208. setupCurlSession(xmlrpc_env * const envP,
  209. CURL * const curlSessionP,
  210. curlTransaction * const curlTransactionP,
  211. xmlrpc_mem_block * const callXmlP,
  212. xmlrpc_mem_block * const responseXmlP) {
  213. curl_easy_setopt(curlSessionP, CURLOPT_POST, 1 );
  214. curl_easy_setopt(curlSessionP, CURLOPT_URL, curlTransactionP->serverUrl);
  215. XMLRPC_MEMBLOCK_APPEND(char, envP, callXmlP, "\0", 1);
  216. if (!envP->fault_occurred) {
  217. curl_easy_setopt(curlSessionP, CURLOPT_POSTFIELDS,
  218. XMLRPC_MEMBLOCK_CONTENTS(char, callXmlP));
  219. curl_easy_setopt(curlSessionP, CURLOPT_FILE, responseXmlP);
  220. curl_easy_setopt(curlSessionP, CURLOPT_HEADER, 0 );
  221. curl_easy_setopt(curlSessionP, CURLOPT_WRITEFUNCTION, collect);
  222. curl_easy_setopt(curlSessionP, CURLOPT_ERRORBUFFER,
  223. curlTransactionP->curlError);
  224. curl_easy_setopt(curlSessionP, CURLOPT_NOPROGRESS, 1);
  225. curl_easy_setopt(curlSessionP, CURLOPT_HTTPHEADER,
  226. curlTransactionP->headerList);
  227. }
  228. }
  229. static void
  230. createCurlTransaction(xmlrpc_env * const envP,
  231. xmlrpc_server_info * const serverP,
  232. xmlrpc_mem_block * const callXmlP,
  233. xmlrpc_mem_block * const responseXmlP,
  234. curlTransaction ** const curlTransactionPP) {
  235. curlTransaction * curlTransactionP;
  236. MALLOCVAR(curlTransactionP);
  237. if (curlTransactionP == NULL)
  238. xmlrpc_env_set_fault_formatted(
  239. envP, XMLRPC_INTERNAL_ERROR,
  240. "No memory to create Curl transaction.");
  241. else {
  242. CURL * const curlSessionP = curl_easy_init();
  243. if (curlSessionP == NULL)
  244. xmlrpc_env_set_fault_formatted(
  245. envP, XMLRPC_INTERNAL_ERROR,
  246. "Could not create Curl session. curl_easy_init() failed.");
  247. else {
  248. curlTransactionP->curlSessionP = curlSessionP;
  249. curlTransactionP->serverUrl = strdup(serverP->_server_url);
  250. if (curlTransactionP->serverUrl == NULL)
  251. xmlrpc_env_set_fault_formatted(
  252. envP, XMLRPC_INTERNAL_ERROR,
  253. "Out of memory to store server URL.");
  254. else {
  255. createCurlHeaderList(envP, serverP,
  256. &curlTransactionP->headerList);
  257. if (!envP->fault_occurred)
  258. setupCurlSession(envP, curlSessionP, curlTransactionP,
  259. callXmlP, responseXmlP);
  260. if (envP->fault_occurred)
  261. strfree(curlTransactionP->serverUrl);
  262. }
  263. if (envP->fault_occurred)
  264. curl_easy_cleanup(curlSessionP);
  265. }
  266. if (envP->fault_occurred)
  267. free(curlTransactionP);
  268. }
  269. *curlTransactionPP = curlTransactionP;
  270. }
  271. static void
  272. destroyCurlTransaction(curlTransaction * const curlTransactionP) {
  273. curl_slist_free_all(curlTransactionP->headerList);
  274. strfree(curlTransactionP->serverUrl);
  275. curl_easy_cleanup(curlTransactionP->curlSessionP);
  276. }
  277. static void
  278. performCurlTransaction(xmlrpc_env * const envP,
  279. curlTransaction * const curlTransactionP) {
  280. CURL * const curlSessionP = curlTransactionP->curlSessionP;
  281. CURLcode res;
  282. res = curl_easy_perform(curlSessionP);
  283. if (res != CURLE_OK)
  284. xmlrpc_env_set_fault_formatted(
  285. envP, XMLRPC_NETWORK_ERROR, "Curl failed to perform "
  286. "HTTP POST request. curl_easy_perform() says: %s",
  287. curlTransactionP->curlError);
  288. else {
  289. CURLcode res;
  290. long http_result;
  291. res = curl_easy_getinfo(curlSessionP, CURLINFO_HTTP_CODE,
  292. &http_result);
  293. if (res != CURLE_OK)
  294. xmlrpc_env_set_fault_formatted(
  295. envP, XMLRPC_INTERNAL_ERROR,
  296. "Curl performed the HTTP POST request, but was "
  297. "unable to say what the HTTP result code was. "
  298. "curl_easy_getinfo(CURLINFO_HTTP_CODE) says: %s",
  299. curlTransactionP->curlError);
  300. else {
  301. if (http_result != 200)
  302. xmlrpc_env_set_fault_formatted(
  303. envP, XMLRPC_NETWORK_ERROR, "HTTP response: %ld",
  304. http_result);
  305. }
  306. }
  307. }
  308. static void
  309. doAsyncRpc2(void * const arg) {
  310. rpc * const rpcP = arg;
  311. xmlrpc_env env;
  312. xmlrpc_env_init(&env);
  313. performCurlTransaction(&env, rpcP->curlTransactionP);
  314. rpcP->complete(rpcP->callInfoP, rpcP->responseXmlP, env);
  315. xmlrpc_env_clean(&env);
  316. }
  317. #ifdef WIN32
  318. static unsigned __stdcall
  319. doAsyncRpc(void * arg) {
  320. doAsyncRpc2(arg);
  321. return 0;
  322. }
  323. #else
  324. static void *
  325. doAsyncRpc(void * arg) {
  326. doAsyncRpc2(arg);
  327. return NULL;
  328. }
  329. #endif
  330. static void
  331. createRpcThread(xmlrpc_env * const envP,
  332. rpc * const rpcP,
  333. pthread_t * const threadP) {
  334. int rc;
  335. rc = pthread_create(threadP, NULL, doAsyncRpc, rpcP);
  336. switch (rc) {
  337. case 0:
  338. break;
  339. case EAGAIN:
  340. xmlrpc_env_set_fault_formatted(
  341. envP, XMLRPC_INTERNAL_ERROR,
  342. "pthread_create() failed: System Resources exceeded.");
  343. break;
  344. case EINVAL:
  345. xmlrpc_env_set_fault_formatted(
  346. envP, XMLRPC_INTERNAL_ERROR,
  347. "pthread_create() failed: Param Error for attr.");
  348. break;
  349. case ENOMEM:
  350. xmlrpc_env_set_fault_formatted(
  351. envP, XMLRPC_INTERNAL_ERROR,
  352. "pthread_create() failed: No memory for new thread.");
  353. break;
  354. default:
  355. xmlrpc_env_set_fault_formatted(
  356. envP, XMLRPC_INTERNAL_ERROR,
  357. "pthread_create() failed: Unrecognized error code %d.", rc);
  358. break;
  359. }
  360. }
  361. static void
  362. rpcCreate(xmlrpc_env * const envP,
  363. struct clientTransport * const clientTransportP,
  364. xmlrpc_server_info * const serverP,
  365. xmlrpc_mem_block * const callXmlP,
  366. xmlrpc_mem_block * const responseXmlP,
  367. transport_asynch_complete complete,
  368. struct call_info * const callInfoP,
  369. rpc ** const rpcPP) {
  370. rpc * rpcP;
  371. MALLOCVAR(rpcP);
  372. if (rpcP == NULL)
  373. xmlrpc_env_set_fault_formatted(
  374. envP, XMLRPC_INTERNAL_ERROR,
  375. "Couldn't allocate memory for rpc object");
  376. else {
  377. rpcP->callInfoP = callInfoP;
  378. rpcP->complete = complete;
  379. rpcP->responseXmlP = responseXmlP;
  380. rpcP->threadExists = FALSE;
  381. createCurlTransaction(envP, serverP,
  382. callXmlP, responseXmlP,
  383. &rpcP->curlTransactionP);
  384. if (!envP->fault_occurred) {
  385. if (complete) {
  386. createRpcThread(envP, rpcP, &rpcP->thread);
  387. if (!envP->fault_occurred)
  388. rpcP->threadExists = TRUE;
  389. }
  390. if (!envP->fault_occurred) {
  391. list_init_header(&rpcP->link, rpcP);
  392. pthread_mutex_lock(&clientTransportP->listLock);
  393. list_add_head(&clientTransportP->rpcList, &rpcP->link);
  394. pthread_mutex_unlock(&clientTransportP->listLock);
  395. }
  396. if (envP->fault_occurred)
  397. destroyCurlTransaction(rpcP->curlTransactionP);
  398. }
  399. if (envP->fault_occurred)
  400. free(rpcP);
  401. }
  402. *rpcPP = rpcP;
  403. }
  404. static void
  405. rpcDestroy(rpc * const rpcP) {
  406. XMLRPC_ASSERT_PTR_OK(rpcP);
  407. XMLRPC_ASSERT(!rpcP->threadExists);
  408. destroyCurlTransaction(rpcP->curlTransactionP);
  409. list_remove(&rpcP->link);
  410. free(rpcP);
  411. }
  412. static void
  413. sendRequest(xmlrpc_env * const envP,
  414. struct clientTransport * const clientTransportP,
  415. xmlrpc_server_info * const serverP,
  416. xmlrpc_mem_block * const callXmlP,
  417. transport_asynch_complete complete,
  418. struct call_info * const callInfoP) {
  419. /*----------------------------------------------------------------------------
  420. Initiate an XML-RPC rpc asynchronously. Don't wait for it to go to
  421. the server.
  422. Unless we return failure, we arrange to have complete() called when
  423. the rpc completes.
  424. This does the 'send_request' operation for a Curl client transport.
  425. -----------------------------------------------------------------------------*/
  426. rpc * rpcP;
  427. xmlrpc_mem_block * responseXmlP;
  428. responseXmlP = XMLRPC_MEMBLOCK_NEW(char, envP, 0);
  429. if (!envP->fault_occurred) {
  430. rpcCreate(envP, clientTransportP, serverP, callXmlP, responseXmlP,
  431. complete, callInfoP,
  432. &rpcP);
  433. if (envP->fault_occurred)
  434. XMLRPC_MEMBLOCK_FREE(char, responseXmlP);
  435. }
  436. /* The user's eventual finish_asynch call will destroy this RPC
  437. and response buffer
  438. */
  439. }
  440. static void *
  441. finishRpc(struct list_head * const headerP,
  442. void * const context ATTR_UNUSED) {
  443. rpc * const rpcP = headerP->itemP;
  444. if (rpcP->threadExists) {
  445. void *status;
  446. int result;
  447. result = pthread_join(rpcP->thread, &status);
  448. rpcP->threadExists = FALSE;
  449. }
  450. XMLRPC_MEMBLOCK_FREE(char, rpcP->responseXmlP);
  451. rpcDestroy(rpcP);
  452. return NULL;
  453. }
  454. static void
  455. finishAsynch(struct clientTransport * const clientTransportP ATTR_UNUSED,
  456. enum timeoutType const timeoutType ATTR_UNUSED,
  457. timeout_t const timeout ATTR_UNUSED) {
  458. /*----------------------------------------------------------------------------
  459. Wait for the threads of all outstanding RPCs to exit and destroy those
  460. RPCs.
  461. This does the 'finish_asynch' operation for a Curl client transport.
  462. -----------------------------------------------------------------------------*/
  463. /* We ignore any timeout request. Some day, we should figure out how
  464. to set an alarm and interrupt running threads.
  465. */
  466. pthread_mutex_lock(&clientTransportP->listLock);
  467. list_foreach(&clientTransportP->rpcList, finishRpc, NULL);
  468. pthread_mutex_unlock(&clientTransportP->listLock);
  469. }
  470. static void
  471. call(xmlrpc_env * const envP,
  472. struct clientTransport * const clientTransportP,
  473. xmlrpc_server_info * const serverP,
  474. xmlrpc_mem_block * const callXmlP,
  475. struct call_info * const callInfoP,
  476. xmlrpc_mem_block ** const responsePP) {
  477. xmlrpc_mem_block * responseXmlP;
  478. rpc * rpcP;
  479. XMLRPC_ASSERT_ENV_OK(envP);
  480. XMLRPC_ASSERT_PTR_OK(serverP);
  481. XMLRPC_ASSERT_PTR_OK(callXmlP);
  482. XMLRPC_ASSERT_PTR_OK(callInfoP);
  483. XMLRPC_ASSERT_PTR_OK(responsePP);
  484. responseXmlP = XMLRPC_MEMBLOCK_NEW(char, envP, 0);
  485. if (!envP->fault_occurred) {
  486. rpcCreate(envP, clientTransportP, serverP, callXmlP, responseXmlP,
  487. NULL, NULL, &rpcP);
  488. if (!envP->fault_occurred) {
  489. performCurlTransaction(envP, rpcP->curlTransactionP);
  490. *responsePP = responseXmlP;
  491. rpcDestroy(rpcP);
  492. }
  493. if (envP->fault_occurred)
  494. XMLRPC_MEMBLOCK_FREE(char, responseXmlP);
  495. }
  496. }
  497. struct clientTransportOps xmlrpc_curl_transport_ops = {
  498. &create,
  499. &destroy,
  500. &sendRequest,
  501. &call,
  502. &finishAsynch,
  503. };