blob: 5726663bca1ecab346650c3c9b70d4b281f44a9d [file] [log] [blame]
/*=============================================================================
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"
#if defined(__BEOS__)
/* Some helpful system header has char==bool, then bool.h does int==bool. */
#define HAVE_BOOL 1
#endif
#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>
#if defined(HAVE_PTHREADS)
# include "xmlrpc_pthreads.h"
#endif
#include <cmcurl/curl/curl.h>
#include <cmcurl/curl/types.h>
#include <cmcurl/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*/
static void xmlrpc_abort(void)
{
abort();
}
struct clientTransport {
#if defined (HAVE_PTHREADS)
pthread_mutex_t listLock;
#endif
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;
#if defined(HAVE_PTHREADS)
pthread_t thread;
#endif
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);
(void)err;
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 {
#ifdef HAVE_PTHREADS
pthread_mutex_init(&transportP->listLock, NULL);
#endif
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));
#if defined(HAVE_PTHREADS)
pthread_mutex_destroy(&clientTransportP->listLock);
#endif
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) {
static char proxy[1024];
static char proxyUser[1024];
int proxy_type = 0;
if ( getenv("HTTP_PROXY") )
{
proxy_type = 1;
if (getenv("HTTP_PROXY_PORT") )
{
sprintf(proxy, "%s:%s", getenv("HTTP_PROXY"), getenv("HTTP_PROXY_PORT"));
}
else
{
sprintf(proxy, "%s", getenv("HTTP_PROXY"));
}
if ( getenv("HTTP_PROXY_TYPE") )
{
/* HTTP/SOCKS4/SOCKS5 */
if ( strcmp(getenv("HTTP_PROXY_TYPE"), "HTTP") == 0 )
{
proxy_type = 1;
}
else if ( strcmp(getenv("HTTP_PROXY_TYPE"), "SOCKS4") == 0 )
{
proxy_type = 2;
}
else if ( strcmp(getenv("HTTP_PROXY_TYPE"), "SOCKS5") == 0 )
{
proxy_type = 3;
}
}
if ( getenv("HTTP_PROXY_USER") )
{
strcpy(proxyUser, getenv("HTTP_PROXY_USER"));
}
if ( getenv("HTTP_PROXY_PASSWD") )
{
strcat(proxyUser, ":");
strcat(proxyUser, getenv("HTTP_PROXY_PASSWD"));
}
}
/* Using proxy */
if ( proxy_type > 0 )
{
curl_easy_setopt(curlSessionP, CURLOPT_PROXY, proxy);
switch (proxy_type)
{
case 2:
curl_easy_setopt(curlSessionP, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4);
break;
case 3:
curl_easy_setopt(curlSessionP, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
break;
default:
curl_easy_setopt(curlSessionP, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
if (*proxyUser)
{
curl_easy_setopt(curlSessionP, CURLOPT_PROXYUSERPWD, proxyUser);
}
}
}
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);
free(curlTransactionP);
}
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 (%d)",
curlTransactionP->curlError, res);
else {
CURLcode crRes;
long http_result;
crRes = curl_easy_getinfo(curlSessionP, CURLINFO_HTTP_CODE,
&http_result);
if (crRes != 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);
}
}
}
#if defined(HAVE_PTHREADS)
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;
}
}
#endif
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) {
#if defined(HAVE_PTHREADS)
createRpcThread(envP, rpcP, &rpcP->thread);
#else
xmlrpc_abort();
#endif
if (!envP->fault_occurred)
rpcP->threadExists = TRUE;
}
if (!envP->fault_occurred) {
list_init_header(&rpcP->link, rpcP);
#if defined(HAVE_PTHREADS)
pthread_mutex_lock(&clientTransportP->listLock);
#endif
list_add_head(&clientTransportP->rpcList, &rpcP->link);
#if defined(HAVE_PTHREADS)
pthread_mutex_unlock(&clientTransportP->listLock);
#endif
}
if (envP->fault_occurred)
destroyCurlTransaction(rpcP->curlTransactionP);
}
if (envP->fault_occurred)
{
free(rpcP);
rpcP = 0; /* set this to null as it is used later on */
}
}
*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) {
#if defined(HAVE_PTHREADS)
void *status;
int result;
result = pthread_join(rpcP->thread, &status);
(void)result;
#else
xmlrpc_abort();
#endif
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.
*/
#if defined(HAVE_PTHREADS)
pthread_mutex_lock(&clientTransportP->listLock);
#else
xmlrpc_abort();
#endif
list_foreach(&clientTransportP->rpcList, finishRpc, NULL);
#if defined(HAVE_PTHREADS)
pthread_mutex_unlock(&clientTransportP->listLock);
#else
xmlrpc_abort();
#endif
}
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,
};