655 lines
19 KiB
C
655 lines
19 KiB
C
/*=============================================================================
|
|
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,
|
|
};
|