X-Git-Url: http://git.maemo.org/git/?p=xmlrpc-c;a=blobdiff_plain;f=lib%2Flibwww_transport%2Fxmlrpc_libwww_transport.c;fp=lib%2Flibwww_transport%2Fxmlrpc_libwww_transport.c;h=61d16acdb4c74d424cd84cd81f4ad1c000097093;hp=0000000000000000000000000000000000000000;hb=ce67d0cdeaa37c3e856e23ae4010480887165630;hpb=e355d4e7962400470f467b88f5568de9c8324475 diff --git a/lib/libwww_transport/xmlrpc_libwww_transport.c b/lib/libwww_transport/xmlrpc_libwww_transport.c new file mode 100644 index 0000000..61d16ac --- /dev/null +++ b/lib/libwww_transport/xmlrpc_libwww_transport.c @@ -0,0 +1,937 @@ +/* Copyright (C) 2001 by First Peer, Inc. All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +** SUCH DAMAGE. */ + +#include "xmlrpc_config.h" + +#include + +#include "bool.h" +#include "mallocvar.h" + +#include "xmlrpc-c/base.h" +#include "xmlrpc-c/base_int.h" +#include "xmlrpc-c/string_int.h" +#include "xmlrpc-c/client.h" +#include "xmlrpc-c/client_int.h" + +/* The libwww interface */ + +/* These headers mistakenly define the macro PACKAGE. As + xmlrpc_config.h already defines PACKAGE according to the package we're + actually part of, this causes a conflict. So we undef here and then + to avoid possible problems with an incorrect PACKAGE, we undef it again + after. +*/ +#undef PACKAGE +#include "WWWLib.h" +#include "WWWHTTP.h" +#include "WWWInit.h" +#undef PACKAGE + +/* Include our libwww SSL headers, if available. */ +#if HAVE_LIBWWW_SSL +#include "WWWSSL.h" +#endif + +#include "xmlrpc_libwww_transport.h" + +/* This value was discovered by Rick Blair. His efforts shaved two seconds +** off of every request processed. Many thanks. */ +#define SMALLEST_LEGAL_LIBWWW_TIMEOUT (21) + +#define XMLRPC_CLIENT_USE_TIMEOUT (2) + + +struct xmlrpc_client_transport { + int saved_flags; + HTList *xmlrpc_conversions; + void * cookieJarP; + /* This is a collection of all the cookies that servers have set + via responses to prior requests. It's not implemented today. + */ + bool tracingOn; +}; + +static struct xmlrpc_client_transport clientTransport; + + +typedef struct { +/*---------------------------------------------------------------------------- + This object represents one RPC. +-----------------------------------------------------------------------------*/ + struct xmlrpc_client_transport * clientTransportP; + + /* These fields are used when performing synchronous calls. */ + bool is_done; + int http_status; + + /* Low-level information used by libwww. */ + HTRequest * request; + HTChunk * response_data; + HTParentAnchor * source_anchor; + HTAnchor * dest_anchor; + + xmlrpc_transport_asynch_complete complete; + struct xmlrpc_call_info * callInfoP; +} rpc; + + + +static void +createCookieJar(xmlrpc_env * const envP ATTR_UNUSED, + void ** const cookieJarP ATTR_UNUSED) { + + /* Cookies not implemented yet */ +} + + + +static void +destroyCookieJar(void * cookieJarP ATTR_UNUSED) { + + /* Cookies not implemented yet */ +} + + + +static void +initLibwww(const char * const appname, + const char * const appversion) { + + /* We initialize the library using a robot profile, because we don't + ** care about redirects or HTTP authentication, and we want to + ** reduce our application footprint as much as possible. */ + HTProfile_newRobot(appname, appversion); + + /* Ilya Goldberg provided the following code to access + ** SSL-protected servers. */ +#if HAVE_LIBWWW_SSL + /* Set the SSL protocol method. By default, it is the highest + ** available protocol. Setting it up to SSL_V23 allows the client + ** to negotiate with the server and set up either TSLv1, SSLv3, + ** or SSLv2 */ + HTSSL_protMethod_set(HTSSL_V23); + + /* Set the certificate verification depth to 2 in order to be able to + ** validate self-signed certificates */ + HTSSL_verifyDepth_set(2); + + /* Register SSL stuff for handling ssl access. The parameter we pass + ** is NO because we can't be pre-emptive with POST */ + HTSSLhttps_init(NO); +#endif /* HAVE_LIBWWW_SSL */ + + /* For interoperability with Frontier, we need to tell libwww *not* + ** to send 'Expect: 100-continue' headers. But if we're not sending + ** these, we shouldn't wait for them. So set our built-in delays to + ** the smallest legal values. */ + HTTP_setBodyWriteDelay (SMALLEST_LEGAL_LIBWWW_TIMEOUT, + SMALLEST_LEGAL_LIBWWW_TIMEOUT); + + /* We attempt to disable all of libwww's chatty, interactive + ** prompts. Let's hope this works. */ + HTAlert_setInteractive(NO); + + /* Here are some alternate setup calls which will help greatly + ** with debugging, should the need arise. + ** + ** HTProfile_newNoCacheClient(appname, appversion); + ** HTAlert_setInteractive(YES); + ** HTPrint_setCallback(printer); + ** HTTrace_setCallback(tracer); */ +} + + + +static void +create(xmlrpc_env * const envP, + int const flags, + const char * const appname, + const char * const appversion, + const struct xmlrpc_xportparms * const transportParmsP ATTR_UNUSED, + size_t const parm_size ATTR_UNUSED, + struct xmlrpc_client_transport ** const handlePP) { +/*---------------------------------------------------------------------------- + This does the 'create' operation for a Libwww client transport. +-----------------------------------------------------------------------------*/ + /* The Libwww transport is not re-entrant -- you can have only one + per program instance. Even if we changed the Xmlrpc-c code not + to use global variables, that wouldn't help because Libwww + itself is not re-entrant. + + So we use a global variable ('clientTransport') for our transport state. + */ + struct xmlrpc_client_transport * const clientTransportP = &clientTransport; + *handlePP = clientTransportP; + + clientTransportP->saved_flags = flags; + + createCookieJar(envP, &clientTransportP->cookieJarP); + if (!envP->fault_occurred) { + if (!(clientTransportP->saved_flags & + XMLRPC_CLIENT_SKIP_LIBWWW_INIT)) + initLibwww(appname, appversion); + + /* Set up our list of conversions for XML-RPC requests. This is a + ** massively stripped-down version of the list in libwww's HTInit.c. + ** XXX - This is hackish; 10.0 is an arbitrary, large quality factor + ** designed to override the built-in converter for XML. */ + clientTransportP->xmlrpc_conversions = HTList_new(); + HTConversion_add(clientTransportP->xmlrpc_conversions, + "text/xml", "*/*", + HTThroughLine, 10.0, 0.0, 0.0); + + if (envP->fault_occurred) + destroyCookieJar(clientTransportP->cookieJarP); + } + if (getenv("XMLRPC_LIBWWW_TRACE")) + clientTransportP->tracingOn = TRUE; + else + clientTransportP->tracingOn = FALSE; +} + + + +static void +destroy(struct xmlrpc_client_transport * const clientTransportP) { +/*---------------------------------------------------------------------------- + This does the 'destroy' operation for a Libwww client transport. +-----------------------------------------------------------------------------*/ + XMLRPC_ASSERT(clientTransportP != NULL); + + if (!(clientTransportP->saved_flags & XMLRPC_CLIENT_SKIP_LIBWWW_INIT)) { + HTProfile_delete(); + } + destroyCookieJar(clientTransportP->cookieJarP); +} + + + +/*========================================================================= +** HTTP Error Reporting +**=======================================================================*/ + +static void +formatLibwwwError(HTRequest * const requestP, + const char ** const msgP) { +/*---------------------------------------------------------------------------- + When something fails in a Libwww request, Libwww generates a stack + of error information (precious little information, of course, in the + Unix tradition) and attaches it to the request object. We make a message + out of that information. + + We rely on Libwww's HTDialog_errorMessage() to do the bulk of the + formatting; we might be able to coax more information out of the request + if we interpreted the error stack directly. +-----------------------------------------------------------------------------*/ + HTList * const errStack = HTRequest_error(requestP); + + if (errStack == NULL) + xmlrpc_asprintf(msgP, "Libwww supplied no error details"); + else { + /* Get an error message from libwww. The middle three + parameters to HTDialog_errorMessage appear to be ignored. + XXX - The documentation for this API is terrible, so we may + be using it incorrectly. + */ + const char * msg = + HTDialog_errorMessage(requestP, HT_A_MESSAGE, HT_MSG_NULL, + "An error occurred", errStack); + + if (msg == NULL) + xmlrpc_asprintf(msgP, "Libwww supplied some error detail, " + "but its HTDialog_errorMessage() subroutine " + "mysteriously failed to interpret it for us."); + else + *msgP = msg; + } +} + + + +static void +set_fault_from_http_request(xmlrpc_env * const envP, + int const status, + HTRequest * const requestP) { +/*---------------------------------------------------------------------------- + Assuming 'requestP' identifies a completed libwww HTTP request, set + *envP according to its success/error status. +-----------------------------------------------------------------------------*/ + XMLRPC_ASSERT_PTR_OK(requestP); + + if (status == 200) { + /* No error. Don't set one in *envP */ + } else { + const char * libwwwMsg; + formatLibwwwError(requestP, &libwwwMsg); + + if (status == -1) + xmlrpc_env_set_fault_formatted( + envP, XMLRPC_NETWORK_ERROR, + "Unable to complete the HTTP request. %s", libwwwMsg); + else { + xmlrpc_env_set_fault_formatted( + envP, XMLRPC_NETWORK_ERROR, + "HTTP request completed with HTTp error %d. %s", + status, libwwwMsg); + } + xmlrpc_strfree(libwwwMsg); + } +} + + + +static BOOL +setCookie(HTRequest * const request, + HTCookie * const cookieP ATTR_UNUSED, + void * const param ATTR_UNUSED) { +/*---------------------------------------------------------------------------- + This is the callback from libwww to tell us the server (according to + its response) wants us to store a cookie (and include it in future + requests). + + We assume that the cookies "domain" is the server's host name + (there are options on the libwww connection to make libwww call this + callback only when that's the case). +-----------------------------------------------------------------------------*/ + rpc * const rpcP = HTRequest_context(request); + struct xmlrpc_client_transport * const clientTransportP = + rpcP->clientTransportP; + + BOOL retval; + + /* Avoid unused variable warning */ + if (clientTransportP->cookieJarP == clientTransportP->cookieJarP) {} + /* Cookies are not implemented today */ + retval = NO; + + return retval; +} + + + +static HTAssocList * +cookiesForHost(const char * const host ATTR_UNUSED, + void * const cookieJarP ATTR_UNUSED) { +/*---------------------------------------------------------------------------- + Find and return all the cookies in jar 'cookieJarP' that are for the + host 'host'. +-----------------------------------------------------------------------------*/ + HTAssocList * hisCookiesP; + + hisCookiesP = HTAssocList_new(); + + if (hisCookiesP) { + /* Cookies are not implemented yet */ + /* Library/Examples/cookie.c in the w3c-libwww source tree contains + an example of constructing the cookie list we are supposed to + return. But today, we return an empty list. + */ + } + return hisCookiesP; +} + + + +static HTAssocList * +findCookie(HTRequest * const request, + void * const param ATTR_UNUSED) { +/*---------------------------------------------------------------------------- + This is the callback from libwww to get the cookies to include in a + request (presumably values the server set via a prior response). +-----------------------------------------------------------------------------*/ + rpc * const rpcP = HTRequest_context(request); + struct xmlrpc_client_transport * const clientTransportP = + rpcP->clientTransportP; + const char * const addr = + HTAnchor_address((HTAnchor *) HTRequest_anchor(request)); + const char * const host = HTParse(addr, "", PARSE_HOST); + + return cookiesForHost(host, clientTransportP->cookieJarP); +} + + + +static void +deleteSourceAnchor(HTParentAnchor * const anchor) { + + /* We need to clear the document first, or else libwww won't + ** really delete the anchor. */ + HTAnchor_setDocument(anchor, NULL); + + /* XXX - Deleting this anchor causes HTLibTerminate to dump core. */ + /* HTAnchor_delete(anchor); */ +} + + + +static void +createSourceAnchor(xmlrpc_env * const envP, + HTParentAnchor ** const sourceAnchorPP, + xmlrpc_mem_block * const xmlP) { + + HTParentAnchor * const sourceAnchorP = HTTmpAnchor(NULL); + + if (sourceAnchorP == NULL) + xmlrpc_env_set_fault_formatted( + envP, XMLRPC_INTERNAL_ERROR, + "Unable to build source anchor. HTTmpAnchor() failed."); + else { + HTAnchor_setDocument(sourceAnchorP, + XMLRPC_MEMBLOCK_CONTENTS(char, xmlP)); + HTAnchor_setFormat(sourceAnchorP, HTAtom_for("text/xml")); + HTAnchor_setLength(sourceAnchorP, XMLRPC_MEMBLOCK_SIZE(char, xmlP)); + + *sourceAnchorPP = sourceAnchorP; + } +} + + + +static void +createDestAnchor(xmlrpc_env * const envP, + HTAnchor ** const destAnchorPP, + const xmlrpc_server_info * const serverP) { + + *destAnchorPP = HTAnchor_findAddress(serverP->_server_url); + + if (*destAnchorPP == NULL) + xmlrpc_env_set_fault_formatted( + envP, XMLRPC_INTERNAL_ERROR, + "Could not build destination anchor. HTAnchor_findAddress() " + "failed."); +} + + + +static void +rpcCreate(xmlrpc_env * const envP, + struct xmlrpc_client_transport * const clientTransportP, + const xmlrpc_server_info * const serverP, + xmlrpc_mem_block * const xmlP, + xmlrpc_transport_asynch_complete complete, + struct xmlrpc_call_info * const callInfoP, + rpc ** const rpcPP) { + + rpc *rpcP; + HTRqHd request_headers; + HTStream *target_stream; + + /* Allocate our structure. */ + MALLOCVAR(rpcP); + XMLRPC_FAIL_IF_NULL(rpcP, envP, XMLRPC_INTERNAL_ERROR, + "Out of memory in rpcCreate()"); + + /* Set up our basic members. */ + rpcP->clientTransportP = clientTransportP; + rpcP->is_done = FALSE; + rpcP->http_status = 0; + rpcP->complete = complete; + rpcP->callInfoP = callInfoP; + + /* Start cookie handler. */ + HTCookie_init(); + HTCookie_setCallbacks(setCookie, NULL, findCookie, NULL); + HTCookie_setCookieMode(HT_COOKIE_ACCEPT | + HT_COOKIE_SEND | + HT_COOKIE_SAME_HOST); + + /* Cookies aren't implemented today; reset. */ + HTCookie_setCookieMode(0); + + /* Create a HTRequest object. */ + rpcP->request = HTRequest_new(); + XMLRPC_FAIL_IF_NULL(rpcP, envP, XMLRPC_INTERNAL_ERROR, + "HTRequest_new failed"); + + /* Install ourselves as the request context. */ + HTRequest_setContext(rpcP->request, rpcP); + + /* XXX - Disable the 'Expect:' header so we can talk to Frontier. */ + request_headers = HTRequest_rqHd(rpcP->request); + request_headers = request_headers & ~HT_C_EXPECT; + HTRequest_setRqHd(rpcP->request, request_headers); + + /* Send an authorization header if we need one. */ + if (serverP->_http_basic_auth) + HTRequest_addCredentials(rpcP->request, "Authorization", + serverP->_http_basic_auth); + + /* Make sure there is no XML conversion handler to steal our data. + ** The 'override' parameter is currently ignored by libwww, so our + ** list of conversions must be designed to co-exist with the built-in + ** conversions. */ + HTRequest_setConversion(rpcP->request, + clientTransportP->xmlrpc_conversions, NO); + + /* Set up our response buffer. */ + target_stream = HTStreamToChunk(rpcP->request, &rpcP->response_data, 0); + XMLRPC_FAIL_IF_NULL(rpcP->response_data, envP, XMLRPC_INTERNAL_ERROR, + "HTStreamToChunk failed"); + XMLRPC_ASSERT(target_stream != NULL); + HTRequest_setOutputStream(rpcP->request, target_stream); + HTRequest_setOutputFormat(rpcP->request, WWW_SOURCE); + + createSourceAnchor(envP, &rpcP->source_anchor, xmlP); + + if (!envP->fault_occurred) { + createDestAnchor(envP, &rpcP->dest_anchor, serverP); + + if (envP->fault_occurred) + /* See below for comments about deleting the source and dest + ** anchors. This is a bit of a black art. */ + deleteSourceAnchor(rpcP->source_anchor); + } + + cleanup: + if (envP->fault_occurred) { + if (rpcP) { + if (rpcP->request) + HTRequest_delete(rpcP->request); + if (rpcP->response_data) + HTChunk_delete(rpcP->response_data); + free(rpcP); + } + } + *rpcPP = rpcP; +} + + + +static void +rpcDestroy(rpc * const rpcP) { + + XMLRPC_ASSERT_PTR_OK(rpcP); + XMLRPC_ASSERT(rpcP->request != XMLRPC_BAD_POINTER); + XMLRPC_ASSERT(rpcP->response_data != XMLRPC_BAD_POINTER); + + /* Junji Kanemaru reports on 05.04.11 that with asynch calls, he + get a segfault, and reversing the order of deleting the request + and the response chunk buffer cured it. But we find no reason + that should be so, so we're waiting for someone to arrive at an + explanation before changing anything. HTRequest_delete() does + destroy the output stream, and the output stream refers to the + response chunk, but HTRequest_delete() explicitly refrains from + destroying the response chunk. And the response chunk does not + refer to the request. + */ + + HTRequest_delete(rpcP->request); + rpcP->request = XMLRPC_BAD_POINTER; + HTChunk_delete(rpcP->response_data); + rpcP->response_data = XMLRPC_BAD_POINTER; + + /* This anchor points to private data, so we're allowed to delete it. */ + deleteSourceAnchor(rpcP->source_anchor); + + /* WARNING: We can't delete the destination anchor, because this points + ** to something in the outside world, and lives in a libwww hash table. + ** Under certain circumstances, this anchor may have been reissued to + ** somebody else. So over time, the anchor cache will grow. If this + ** is a problem for your application, read the documentation for + ** HTAnchor_deleteAll. + ** + ** However, we CAN check to make sure that no documents have been + ** attached to the anchor. This assertion may fail if you're using + ** libwww for something else, so please feel free to comment it out. */ + /* XMLRPC_ASSERT(HTAnchor_document(rpcP->dest_anchor) == NULL); + */ + + HTCookie_deleteCallbacks(); + HTCookie_terminate(); + + free(rpcP); +} + + + +static void +extract_response_chunk(xmlrpc_env * const envP, + rpc * const rpcP, + xmlrpc_mem_block ** const responseXmlPP) { + + /* Check to make sure that w3c-libwww actually sent us some data. + ** XXX - This may happen if libwww is shut down prematurely, believe it + ** or not--we'll get a 200 OK and no data. Gag me with a bogus design + ** decision. This may also fail if some naughty libwww converter + ** ate our data unexpectedly. */ + if (!HTChunk_data(rpcP->response_data)) + xmlrpc_env_set_fault(envP, XMLRPC_NETWORK_ERROR, + "w3c-libwww returned no data"); + else { + *responseXmlPP = XMLRPC_MEMBLOCK_NEW(char, envP, 0); + if (!envP->fault_occurred) { + if (rpcP->clientTransportP->tracingOn) { + fprintf(stderr, "HTTP chunk received: %u bytes: '%.*s'", + HTChunk_size(rpcP->response_data), + HTChunk_size(rpcP->response_data), + HTChunk_data(rpcP->response_data)); + } + + XMLRPC_MEMBLOCK_APPEND(char, envP, *responseXmlPP, + HTChunk_data(rpcP->response_data), + HTChunk_size(rpcP->response_data)); + if (envP->fault_occurred) + XMLRPC_MEMBLOCK_FREE(char, *responseXmlPP); + } + } +} + + + +static int +synch_terminate_handler(HTRequest * const request, + HTResponse * const response ATTR_UNUSED, + void * const param ATTR_UNUSED, + int const status) { +/*---------------------------------------------------------------------------- + This is a libwww request completion handler. + + HTEventList_newLoop() calls this when it completes a request (with this + registered as the completion handler). +-----------------------------------------------------------------------------*/ + rpc *rpcP; + + rpcP = HTRequest_context(request); + + rpcP->is_done = TRUE; + rpcP->http_status = status; + + HTEventList_stopLoop(); + + return HT_OK; +} + + + +static void +call(xmlrpc_env * const envP, + struct xmlrpc_client_transport * const clientTransportP, + const xmlrpc_server_info * const serverP, + xmlrpc_mem_block * const xmlP, + xmlrpc_mem_block ** const responsePP) { +/*---------------------------------------------------------------------------- + This does the 'call' operation for a Libwww client transport. +-----------------------------------------------------------------------------*/ + rpc * rpcP; + + XMLRPC_ASSERT_ENV_OK(envP); + XMLRPC_ASSERT_PTR_OK(serverP); + XMLRPC_ASSERT_PTR_OK(xmlP); + XMLRPC_ASSERT_PTR_OK(responsePP); + + rpcCreate(envP, clientTransportP, serverP, xmlP, NULL, NULL, &rpcP); + if (!envP->fault_occurred) { + int ok; + + /* Install our request handler. */ + HTRequest_addAfter(rpcP->request, &synch_terminate_handler, + NULL, NULL, HT_ALL, HT_FILTER_LAST, NO); + + /* Start our request running. */ + ok = HTPostAnchor(rpcP->source_anchor, + rpcP->dest_anchor, + rpcP->request); + if (!ok) + xmlrpc_env_set_fault( + envP, XMLRPC_NETWORK_ERROR, + "Libwww HTPostAnchor() failed to start POST request"); + else { + /* Run our event-processing loop. HTEventList_newLoop() + is what calls synch_terminate_handler(), by virtue of + it being registered as a handler. It may return for + other reasons than the request being complete, though. + so we call it in a loop until synch_terminate_handler() + really has been called. + */ + while (!rpcP->is_done) + HTEventList_newLoop(); + + /* Fail if we didn't get a "200 OK" response from the server */ + if (rpcP->http_status != 200) + set_fault_from_http_request( + envP, rpcP->http_status, + rpcP->request); + else { + /* XXX - Check to make sure response type is text/xml here. */ + + extract_response_chunk(envP, rpcP, responsePP); + } + } + rpcDestroy(rpcP); + } +} + + + +/*========================================================================= +** Event Loop +**========================================================================= +** We manage a fair bit of internal state about our event loop. This is +** needed to determine when (and if) we should exit the loop. +*/ + +static int outstanding_asynch_calls = 0; +static int event_loop_flags = 0; +static int timer_called = 0; + +static void +register_asynch_call(void) { + XMLRPC_ASSERT(outstanding_asynch_calls >= 0); + outstanding_asynch_calls++; +} + + + +static void +unregister_asynch_call(void) { + + XMLRPC_ASSERT(outstanding_asynch_calls > 0); + outstanding_asynch_calls--; + if (outstanding_asynch_calls == 0) + HTEventList_stopLoop(); +} + + + +static int +timer_callback(HTTimer * const timer ATTR_UNUSED, + void * const user_data ATTR_UNUSED, + HTEventType const event ATTR_UNUSED) { +/*---------------------------------------------------------------------------- + A handy timer callback which cancels the running event loop. +-----------------------------------------------------------------------------*/ + XMLRPC_ASSERT(event == HTEvent_TIMEOUT); + timer_called = 1; + HTEventList_stopLoop(); + + /* XXX - The meaning of this return value is undocumented, but close + ** inspection of libwww's source suggests that we want to return HT_OK. */ + return HT_OK; +} + + + +static void +eventLoopRun(int const flags, + xmlrpc_timeout const milliseconds) { +/*---------------------------------------------------------------------------- + Process all responses from outstanding requests as they come in. + Return when there are no more outstanding responses. + + Or, if 'flags' has the XMLRPC_CLIENT_USE_TIMEOUT flag set, return + when 'milliseconds' milliseconds have elapsed, regardless of whether + there are still outstanding responses. + + The processing we do consists of telling libwww to process the + completion of the libwww request. That normally includes calling + the xmlrpc_libwww_transport request termination handler, because + the submitter of the libwww request would have registered that as a + callback. +-----------------------------------------------------------------------------*/ + if (outstanding_asynch_calls > 0) { + HTTimer *timer; + + event_loop_flags = flags; + + /* Run an appropriate event loop. The HTEeventList_newLoop() + is what calls asynch_terminate_handler(), by virtue of it + being registered as a handler. + */ + if (event_loop_flags & XMLRPC_CLIENT_USE_TIMEOUT) { + + /* Run our event loop with a timer. Note that we need to be very + ** careful about race conditions--timers can be fired in either + ** HTimer_new or HTEventList_newLoop. And if our callback were to + ** get called before we entered the loop, we would never exit. + ** So we use a private flag of our own--we can't even rely on + ** HTTimer_hasTimerExpired, because that only checks the time, + ** not whether our callback has been run. Yuck. */ + timer_called = 0; + timer = HTTimer_new(NULL, &timer_callback, NULL, + milliseconds, YES, NO); + XMLRPC_ASSERT(timer != NULL); + if (!timer_called) + HTEventList_newLoop(); + HTTimer_delete(timer); + + } else { + /* Run our event loop without a timer. */ + HTEventList_newLoop(); + } + + /* Reset our flags, so we don't interfere with direct calls to the + ** libwww event loop functions. */ + event_loop_flags = 0; + } else { + /* There are *no* calls to process. This may mean that none + of the asynch calls were ever set up, and the client's + callbacks have already been called with an error, or that + all outstanding calls were completed during a previous + synchronous call. + */ + } +} + + + +static void +finishAsynch( + struct xmlrpc_client_transport * const clientTransportP ATTR_UNUSED, + xmlrpc_timeoutType const timeoutType, + xmlrpc_timeout const timeout) { +/*---------------------------------------------------------------------------- + This does the 'finish_asynch' operation for a Libwww client transport. +-----------------------------------------------------------------------------*/ + eventLoopRun(timeoutType == timeout_yes ? XMLRPC_CLIENT_USE_TIMEOUT : 0, + timeout); +} + + + +static int +asynch_terminate_handler(HTRequest * const request, + HTResponse * const response ATTR_UNUSED, + void * const param ATTR_UNUSED, + int const status) { +/*---------------------------------------------------------------------------- + Handle the completion of a libwww request. + + This is the bottom half of the xmlrpc_libwww_transport asynchronous + call dispatcher. It's what the dispatcher registers with libwww as + a "local after filter" so that libwww calls it when a request that + xmlrpc_libwww_transport submitted to it is complete. + + We destroy the RPC, including the request which is our argument. + Strange as that may seem, it is apparently legal for an after filter + to destroy the request that was passed to it -- or not. +-----------------------------------------------------------------------------*/ + xmlrpc_env env; + rpc * rpcP; + xmlrpc_mem_block * responseXmlP; + + XMLRPC_ASSERT_PTR_OK(request); + + xmlrpc_env_init(&env); + + rpcP = HTRequest_context(request); + + /* Unregister this call from the event loop. Among other things, this + ** may decide to stop the event loop. + **/ + unregister_asynch_call(); + + /* Give up if an error occurred. */ + if (status != 200) + set_fault_from_http_request(&env, status, request); + else { + /* XXX - Check to make sure response type is text/xml here. */ + extract_response_chunk(&env, rpcP, &responseXmlP); + } + rpcP->complete(rpcP->callInfoP, responseXmlP, env); + + if (!env.fault_occurred) + XMLRPC_MEMBLOCK_FREE(char, responseXmlP); + + rpcDestroy(rpcP); + + xmlrpc_env_clean(&env); + return HT_OK; +} + + + +static void +sendRequest(xmlrpc_env * const envP, + struct xmlrpc_client_transport * const clientTransportP, + const xmlrpc_server_info * const serverP, + xmlrpc_mem_block * const xmlP, + xmlrpc_transport_asynch_complete complete, + struct xmlrpc_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 Libwww client transport. +-----------------------------------------------------------------------------*/ + rpc * rpcP; + + XMLRPC_ASSERT_PTR_OK(envP); + XMLRPC_ASSERT_PTR_OK(serverP); + XMLRPC_ASSERT_PTR_OK(xmlP); + XMLRPC_ASSERT_PTR_OK(callInfoP); + + rpcCreate(envP, clientTransportP, serverP, xmlP, complete, callInfoP, + &rpcP); + if (!envP->fault_occurred) { + int ok; + + /* Install our request handler. */ + HTRequest_addAfter(rpcP->request, &asynch_terminate_handler, + NULL, NULL, HT_ALL, HT_FILTER_LAST, NO); + + /* Register our asynchronous call with the event loop. This means + the user's callback is guaranteed to be called eventually. + */ + register_asynch_call(); + + /* This makes the TCP connection and sends the XML to the server + as an HTTP POST request. + + There was a comment here that said this might return failure + (!ok) and still invoke our completion handler + (asynch_terminate_handler(). The code attempted to deal with + that. Well, it's impossible to deal with that, so if it really + happens, we must fix Libwww. -Bryan 04.11.23. + */ + + ok = HTPostAnchor(rpcP->source_anchor, + rpcP->dest_anchor, + rpcP->request); + if (!ok) { + unregister_asynch_call(); + xmlrpc_env_set_fault(envP, XMLRPC_NETWORK_ERROR, + "Libwww (HTPostAnchor()) failed to start the " + "POST request."); + } + if (envP->fault_occurred) + rpcDestroy(rpcP); + } +} + + + +struct xmlrpc_client_transport_ops xmlrpc_libwww_transport_ops = { + NULL, + NULL, + &create, + &destroy, + &sendRequest, + &call, + &finishAsynch, +};