| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654 |
- /*=============================================================================
- xmlrpc_curl_transport
- ===============================================================================
- Curl-based client transport for Xmlrpc-c
- By Bryan Henderson 04.12.10.
- Contributed to the public domain by its author.
- =============================================================================*/
- #include "xmlrpc_config.h"
- #include "bool.h"
- #include "mallocvar.h"
- #include "linklist.h"
- #include "casprintf.h"
- #include "xmlrpc.h"
- #include "xmlrpc_int.h"
- #include "xmlrpc_client.h"
- #include "xmlrpc_client_int.h"
- #include <string.h>
- #include <stdlib.h>
- #include <errno.h>
- #include "xmlrpc_pthreads.h"
- #include <curl/curl.h>
- #include <curl/types.h>
- #include <curl/easy.h>
- #ifndef WIN32
- # include <unistd.h>
- #endif
- #if defined (WIN32) && defined(_DEBUG)
- # include <crtdbg.h>
- # define new DEBUG_NEW
- # define malloc(size) _malloc_dbg( size, _NORMAL_BLOCK, __FILE__, __LINE__)
- # undef THIS_FILE
- static char THIS_FILE[] = __FILE__;
- #endif /*WIN32 && _DEBUG*/
- struct clientTransport {
- pthread_mutex_t listLock;
- struct list_head rpcList;
- /* List of all RPCs that exist for this transport. An RPC exists
- from the time the user requests it until the time the user
- acknowledges it is done.
- */
- };
- typedef struct {
- /* This is all stuff that really ought to be in the CURL object,
- but the Curl library is a little too simple for that. So we
- build a layer on top of it, and call it a "transaction," as
- distinct from the Curl "session" represented by the CURL object.
- */
- CURL * curlSessionP;
- /* Handle for Curl library session object */
- char curlError[CURL_ERROR_SIZE];
- /* Error message from Curl */
- struct curl_slist * headerList;
- /* The HTTP headers for the transaction */
- const char * serverUrl; /* malloc'ed - belongs to this object */
- } curlTransaction;
- typedef struct {
- struct list_head link; /* link in transport's list of RPCs */
- curlTransaction * curlTransactionP;
- /* The object which does the HTTP transaction, with no knowledge
- of XML-RPC or Xmlrpc-c.
- */
- xmlrpc_mem_block * responseXmlP;
- xmlrpc_bool threadExists;
- pthread_t thread;
- transport_asynch_complete complete;
- /* Routine to call to complete the RPC after it is complete HTTP-wise.
- NULL if none.
- */
- struct call_info * callInfoP;
- /* User's identifier for this RPC */
- } rpc;
- static size_t
- collect(void * const ptr,
- size_t const size,
- size_t const nmemb,
- FILE * const stream) {
- /*----------------------------------------------------------------------------
- This is a Curl output function. Curl calls this to deliver the
- HTTP response body. Curl thinks it's writing to a POSIX stream.
- -----------------------------------------------------------------------------*/
- xmlrpc_mem_block * const responseXmlP = (xmlrpc_mem_block *) stream;
- char * const buffer = ptr;
- size_t const length = nmemb * size;
- size_t retval;
- xmlrpc_env env;
- xmlrpc_env_init(&env);
- xmlrpc_mem_block_append(&env, responseXmlP, buffer, length);
- if (env.fault_occurred)
- retval = (size_t)-1;
- else
- /* Really? Shouldn't it be like fread() and return 'nmemb'? */
- retval = length;
-
- return retval;
- }
- static void
- initWindowsStuff(xmlrpc_env * const envP) {
- #if defined (WIN32)
- /* This is CRITICAL so that cURL-Win32 works properly! */
- WORD wVersionRequested;
- WSADATA wsaData;
- int err;
- wVersionRequested = MAKEWORD(1, 1);
-
- err = WSAStartup(wVersionRequested, &wsaData);
- if (LOBYTE(wsaData.wVersion) != 1 ||
- HIBYTE( wsaData.wVersion) != 1) {
- /* Tell the user that we couldn't find a useable */
- /* winsock.dll. */
- WSACleanup();
- xmlrpc_env_set_fault_formatted(
- envP, XMLRPC_INTERNAL_ERROR, "Winsock reported that "
- "it does not implement the requested version 1.1.");
- }
- #else
- if (0)
- envP->fault_occurred = TRUE; /* Avoid unused parm warning */
- #endif
- }
- static void
- create(xmlrpc_env * const envP,
- int const flags ATTR_UNUSED,
- const char * const appname ATTR_UNUSED,
- const char * const appversion ATTR_UNUSED,
- struct clientTransport ** const handlePP) {
- /*----------------------------------------------------------------------------
- This does the 'create' operation for a Curl client transport.
- -----------------------------------------------------------------------------*/
- struct clientTransport * transportP;
- initWindowsStuff(envP);
- MALLOCVAR(transportP);
- if (transportP == NULL)
- xmlrpc_env_set_fault_formatted(
- envP, XMLRPC_INTERNAL_ERROR,
- "Unable to allocate transport descriptor.");
- else {
- pthread_mutex_init(&transportP->listLock, NULL);
-
- list_make_empty(&transportP->rpcList);
- /*
- * This is the main global constructor for the app. Call this before
- * _any_ libcurl usage. If this fails, *NO* libcurl functions may be
- * used, or havoc may be the result.
- */
- curl_global_init(CURL_GLOBAL_ALL);
- /* The above makes it look like Curl is not re-entrant. We should
- check into that.
- */
- *handlePP = transportP;
- }
- }
- static void
- termWindowStuff(void) {
- #if defined (WIN32)
- WSACleanup();
- #endif
- }
- static void
- destroy(struct clientTransport * const clientTransportP) {
- /*----------------------------------------------------------------------------
- This does the 'destroy' operation for a Libwww client transport.
- -----------------------------------------------------------------------------*/
- XMLRPC_ASSERT(clientTransportP != NULL);
- XMLRPC_ASSERT(list_is_empty(&clientTransportP->rpcList));
- pthread_mutex_destroy(&clientTransportP->listLock);
- curl_global_cleanup();
- termWindowStuff();
- free(clientTransportP);
- }
- static void
- createCurlHeaderList(xmlrpc_env * const envP,
- xmlrpc_server_info * const serverP,
- struct curl_slist ** const headerListP) {
- struct curl_slist * headerList;
- headerList = NULL; /* initial value */
- headerList = curl_slist_append(headerList, "Content-Type: text/xml");
- if (headerList == NULL)
- xmlrpc_env_set_fault_formatted(
- envP, XMLRPC_INTERNAL_ERROR,
- "Could not add header. curl_slist_append() failed.");
- else {
- /* Send an authorization header if we need one. */
- if (serverP->_http_basic_auth) {
- /* Make the authentication header "Authorization: " */
- /* we need 15 + length of _http_basic_auth + 1 for null */
- char * const authHeader =
- malloc(strlen(serverP->_http_basic_auth) + 15 + 1);
-
- if (authHeader == NULL)
- xmlrpc_env_set_fault_formatted(
- envP, XMLRPC_INTERNAL_ERROR,
- "Couldn't allocate memory for authentication header");
- else {
- memcpy(authHeader,"Authorization: ", 15);
- memcpy(authHeader + 15, serverP->_http_basic_auth,
- strlen(serverP->_http_basic_auth) + 1);
- headerList = curl_slist_append(headerList, authHeader);
- if (headerList == NULL)
- xmlrpc_env_set_fault_formatted(
- envP, XMLRPC_INTERNAL_ERROR,
- "Could not add authentication header. "
- "curl_slist_append() failed.");
- free(authHeader);
- }
- }
- if (envP->fault_occurred)
- free(headerList);
- }
- *headerListP = headerList;
- }
- static void
- setupCurlSession(xmlrpc_env * const envP,
- CURL * const curlSessionP,
- curlTransaction * const curlTransactionP,
- xmlrpc_mem_block * const callXmlP,
- xmlrpc_mem_block * const responseXmlP) {
- curl_easy_setopt(curlSessionP, CURLOPT_POST, 1 );
- curl_easy_setopt(curlSessionP, CURLOPT_URL, curlTransactionP->serverUrl);
- XMLRPC_MEMBLOCK_APPEND(char, envP, callXmlP, "\0", 1);
- if (!envP->fault_occurred) {
- curl_easy_setopt(curlSessionP, CURLOPT_POSTFIELDS,
- XMLRPC_MEMBLOCK_CONTENTS(char, callXmlP));
-
- curl_easy_setopt(curlSessionP, CURLOPT_FILE, responseXmlP);
- curl_easy_setopt(curlSessionP, CURLOPT_HEADER, 0 );
- curl_easy_setopt(curlSessionP, CURLOPT_WRITEFUNCTION, collect);
- curl_easy_setopt(curlSessionP, CURLOPT_ERRORBUFFER,
- curlTransactionP->curlError);
- curl_easy_setopt(curlSessionP, CURLOPT_NOPROGRESS, 1);
-
- curl_easy_setopt(curlSessionP, CURLOPT_HTTPHEADER,
- curlTransactionP->headerList);
- }
- }
- static void
- createCurlTransaction(xmlrpc_env * const envP,
- xmlrpc_server_info * const serverP,
- xmlrpc_mem_block * const callXmlP,
- xmlrpc_mem_block * const responseXmlP,
- curlTransaction ** const curlTransactionPP) {
- curlTransaction * curlTransactionP;
- MALLOCVAR(curlTransactionP);
- if (curlTransactionP == NULL)
- xmlrpc_env_set_fault_formatted(
- envP, XMLRPC_INTERNAL_ERROR,
- "No memory to create Curl transaction.");
- else {
- CURL * const curlSessionP = curl_easy_init();
-
- if (curlSessionP == NULL)
- xmlrpc_env_set_fault_formatted(
- envP, XMLRPC_INTERNAL_ERROR,
- "Could not create Curl session. curl_easy_init() failed.");
- else {
- curlTransactionP->curlSessionP = curlSessionP;
- curlTransactionP->serverUrl = strdup(serverP->_server_url);
- if (curlTransactionP->serverUrl == NULL)
- xmlrpc_env_set_fault_formatted(
- envP, XMLRPC_INTERNAL_ERROR,
- "Out of memory to store server URL.");
- else {
- createCurlHeaderList(envP, serverP,
- &curlTransactionP->headerList);
- if (!envP->fault_occurred)
- setupCurlSession(envP, curlSessionP, curlTransactionP,
- callXmlP, responseXmlP);
- if (envP->fault_occurred)
- strfree(curlTransactionP->serverUrl);
- }
- if (envP->fault_occurred)
- curl_easy_cleanup(curlSessionP);
- }
- if (envP->fault_occurred)
- free(curlTransactionP);
- }
- *curlTransactionPP = curlTransactionP;
- }
- static void
- destroyCurlTransaction(curlTransaction * const curlTransactionP) {
- curl_slist_free_all(curlTransactionP->headerList);
- strfree(curlTransactionP->serverUrl);
- curl_easy_cleanup(curlTransactionP->curlSessionP);
- }
- static void
- performCurlTransaction(xmlrpc_env * const envP,
- curlTransaction * const curlTransactionP) {
- CURL * const curlSessionP = curlTransactionP->curlSessionP;
- CURLcode res;
- res = curl_easy_perform(curlSessionP);
-
- if (res != CURLE_OK)
- xmlrpc_env_set_fault_formatted(
- envP, XMLRPC_NETWORK_ERROR, "Curl failed to perform "
- "HTTP POST request. curl_easy_perform() says: %s",
- curlTransactionP->curlError);
- else {
- CURLcode res;
- long http_result;
- res = curl_easy_getinfo(curlSessionP, CURLINFO_HTTP_CODE,
- &http_result);
- if (res != CURLE_OK)
- xmlrpc_env_set_fault_formatted(
- envP, XMLRPC_INTERNAL_ERROR,
- "Curl performed the HTTP POST request, but was "
- "unable to say what the HTTP result code was. "
- "curl_easy_getinfo(CURLINFO_HTTP_CODE) says: %s",
- curlTransactionP->curlError);
- else {
- if (http_result != 200)
- xmlrpc_env_set_fault_formatted(
- envP, XMLRPC_NETWORK_ERROR, "HTTP response: %ld",
- http_result);
- }
- }
- }
- static void
- doAsyncRpc2(void * const arg) {
- rpc * const rpcP = arg;
- xmlrpc_env env;
- xmlrpc_env_init(&env);
- performCurlTransaction(&env, rpcP->curlTransactionP);
- rpcP->complete(rpcP->callInfoP, rpcP->responseXmlP, env);
- xmlrpc_env_clean(&env);
- }
- #ifdef WIN32
- static unsigned __stdcall
- doAsyncRpc(void * arg) {
- doAsyncRpc2(arg);
- return 0;
- }
- #else
- static void *
- doAsyncRpc(void * arg) {
- doAsyncRpc2(arg);
- return NULL;
- }
- #endif
- static void
- createRpcThread(xmlrpc_env * const envP,
- rpc * const rpcP,
- pthread_t * const threadP) {
- int rc;
- rc = pthread_create(threadP, NULL, doAsyncRpc, rpcP);
- switch (rc) {
- case 0:
- break;
- case EAGAIN:
- xmlrpc_env_set_fault_formatted(
- envP, XMLRPC_INTERNAL_ERROR,
- "pthread_create() failed: System Resources exceeded.");
- break;
- case EINVAL:
- xmlrpc_env_set_fault_formatted(
- envP, XMLRPC_INTERNAL_ERROR,
- "pthread_create() failed: Param Error for attr.");
- break;
- case ENOMEM:
- xmlrpc_env_set_fault_formatted(
- envP, XMLRPC_INTERNAL_ERROR,
- "pthread_create() failed: No memory for new thread.");
- break;
- default:
- xmlrpc_env_set_fault_formatted(
- envP, XMLRPC_INTERNAL_ERROR,
- "pthread_create() failed: Unrecognized error code %d.", rc);
- break;
- }
- }
- static void
- rpcCreate(xmlrpc_env * const envP,
- struct clientTransport * const clientTransportP,
- xmlrpc_server_info * const serverP,
- xmlrpc_mem_block * const callXmlP,
- xmlrpc_mem_block * const responseXmlP,
- transport_asynch_complete complete,
- struct call_info * const callInfoP,
- rpc ** const rpcPP) {
- rpc * rpcP;
- MALLOCVAR(rpcP);
- if (rpcP == NULL)
- xmlrpc_env_set_fault_formatted(
- envP, XMLRPC_INTERNAL_ERROR,
- "Couldn't allocate memory for rpc object");
- else {
- rpcP->callInfoP = callInfoP;
- rpcP->complete = complete;
- rpcP->responseXmlP = responseXmlP;
- rpcP->threadExists = FALSE;
- createCurlTransaction(envP, serverP,
- callXmlP, responseXmlP,
- &rpcP->curlTransactionP);
- if (!envP->fault_occurred) {
- if (complete) {
- createRpcThread(envP, rpcP, &rpcP->thread);
- if (!envP->fault_occurred)
- rpcP->threadExists = TRUE;
- }
- if (!envP->fault_occurred) {
- list_init_header(&rpcP->link, rpcP);
- pthread_mutex_lock(&clientTransportP->listLock);
- list_add_head(&clientTransportP->rpcList, &rpcP->link);
- pthread_mutex_unlock(&clientTransportP->listLock);
- }
- if (envP->fault_occurred)
- destroyCurlTransaction(rpcP->curlTransactionP);
- }
- if (envP->fault_occurred)
- free(rpcP);
- }
- *rpcPP = rpcP;
- }
- static void
- rpcDestroy(rpc * const rpcP) {
- XMLRPC_ASSERT_PTR_OK(rpcP);
- XMLRPC_ASSERT(!rpcP->threadExists);
- destroyCurlTransaction(rpcP->curlTransactionP);
- list_remove(&rpcP->link);
- free(rpcP);
- }
- static void
- sendRequest(xmlrpc_env * const envP,
- struct clientTransport * const clientTransportP,
- xmlrpc_server_info * const serverP,
- xmlrpc_mem_block * const callXmlP,
- transport_asynch_complete complete,
- struct call_info * const callInfoP) {
- /*----------------------------------------------------------------------------
- Initiate an XML-RPC rpc asynchronously. Don't wait for it to go to
- the server.
- Unless we return failure, we arrange to have complete() called when
- the rpc completes.
- This does the 'send_request' operation for a Curl client transport.
- -----------------------------------------------------------------------------*/
- rpc * rpcP;
- xmlrpc_mem_block * responseXmlP;
- responseXmlP = XMLRPC_MEMBLOCK_NEW(char, envP, 0);
- if (!envP->fault_occurred) {
- rpcCreate(envP, clientTransportP, serverP, callXmlP, responseXmlP,
- complete, callInfoP,
- &rpcP);
- if (envP->fault_occurred)
- XMLRPC_MEMBLOCK_FREE(char, responseXmlP);
- }
- /* The user's eventual finish_asynch call will destroy this RPC
- and response buffer
- */
- }
- static void *
- finishRpc(struct list_head * const headerP,
- void * const context ATTR_UNUSED) {
-
- rpc * const rpcP = headerP->itemP;
- if (rpcP->threadExists) {
- void *status;
- int result;
- result = pthread_join(rpcP->thread, &status);
-
- rpcP->threadExists = FALSE;
- }
- XMLRPC_MEMBLOCK_FREE(char, rpcP->responseXmlP);
- rpcDestroy(rpcP);
- return NULL;
- }
- static void
- finishAsynch(struct clientTransport * const clientTransportP ATTR_UNUSED,
- enum timeoutType const timeoutType ATTR_UNUSED,
- timeout_t const timeout ATTR_UNUSED) {
- /*----------------------------------------------------------------------------
- Wait for the threads of all outstanding RPCs to exit and destroy those
- RPCs.
- This does the 'finish_asynch' operation for a Curl client transport.
- -----------------------------------------------------------------------------*/
- /* We ignore any timeout request. Some day, we should figure out how
- to set an alarm and interrupt running threads.
- */
- pthread_mutex_lock(&clientTransportP->listLock);
- list_foreach(&clientTransportP->rpcList, finishRpc, NULL);
- pthread_mutex_unlock(&clientTransportP->listLock);
- }
- static void
- call(xmlrpc_env * const envP,
- struct clientTransport * const clientTransportP,
- xmlrpc_server_info * const serverP,
- xmlrpc_mem_block * const callXmlP,
- struct call_info * const callInfoP,
- xmlrpc_mem_block ** const responsePP) {
- xmlrpc_mem_block * responseXmlP;
- rpc * rpcP;
- XMLRPC_ASSERT_ENV_OK(envP);
- XMLRPC_ASSERT_PTR_OK(serverP);
- XMLRPC_ASSERT_PTR_OK(callXmlP);
- XMLRPC_ASSERT_PTR_OK(callInfoP);
- XMLRPC_ASSERT_PTR_OK(responsePP);
- responseXmlP = XMLRPC_MEMBLOCK_NEW(char, envP, 0);
- if (!envP->fault_occurred) {
- rpcCreate(envP, clientTransportP, serverP, callXmlP, responseXmlP,
- NULL, NULL, &rpcP);
- if (!envP->fault_occurred) {
- performCurlTransaction(envP, rpcP->curlTransactionP);
-
- *responsePP = responseXmlP;
-
- rpcDestroy(rpcP);
- }
- if (envP->fault_occurred)
- XMLRPC_MEMBLOCK_FREE(char, responseXmlP);
- }
- }
- struct clientTransportOps xmlrpc_curl_transport_ops = {
- &create,
- &destroy,
- &sendRequest,
- &call,
- &finishAsynch,
- };
|