summaryrefslogtreecommitdiffstats
path: root/Utilities/cmxmlrpc/xmlrpc_curl_transport.c
diff options
context:
space:
mode:
Diffstat (limited to 'Utilities/cmxmlrpc/xmlrpc_curl_transport.c')
-rw-r--r--Utilities/cmxmlrpc/xmlrpc_curl_transport.c651
1 files changed, 651 insertions, 0 deletions
diff --git a/Utilities/cmxmlrpc/xmlrpc_curl_transport.c b/Utilities/cmxmlrpc/xmlrpc_curl_transport.c
new file mode 100644
index 0000000..37d3dea
--- /dev/null
+++ b/Utilities/cmxmlrpc/xmlrpc_curl_transport.c
@@ -0,0 +1,651 @@
+/*=============================================================================
+ 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 <pthread.h>
+
+#include <curl/curl.h>
+#include <curl/types.h>
+#include <curl/easy.h>
+
+#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);
+}
+
+
+#include <unistd.h>
+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,
+};