921 lines
33 KiB
C
921 lines
33 KiB
C
/* 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 <stddef.h>
|
|
|
|
#include "bool.h"
|
|
#include "mallocvar.h"
|
|
#include "xmlrpc-c/base.h"
|
|
#include "xmlrpc-c/base_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 <igg@mit.edu> 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
|
|
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 {
|
|
/* 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.
|
|
*/
|
|
HTList * const errStack = HTRequest_error(requestP);
|
|
|
|
if (errStack == NULL) {
|
|
/* I think this is probably impossible, because we didn't get
|
|
status 200, but I don't completely understand HTTP and libwww.
|
|
*/
|
|
xmlrpc_env_set_fault_formatted(
|
|
envP, XMLRPC_NETWORK_ERROR,
|
|
"HTTP error #%d occurred, but there was no additional "
|
|
"error information supplied", status);
|
|
} else {
|
|
const char * const msg =
|
|
HTDialog_errorMessage(requestP, HT_A_MESSAGE, HT_MSG_NULL,
|
|
"An error occurred", errStack);
|
|
|
|
if (msg == NULL)
|
|
xmlrpc_env_set_fault_formatted(
|
|
envP, XMLRPC_NETWORK_ERROR,
|
|
"HTTP error #%d occurred. Libwww's "
|
|
"HTDialog_errorMessage() routine was mysteriously "
|
|
"unable to interpret the additional error information, "
|
|
"so we have none to report.", status);
|
|
else {
|
|
/* Set our fault. Note that this may inlcude line breaks,
|
|
because 'msg' may. We should fix that. Formatting for
|
|
display is none of our business at this level.
|
|
*/
|
|
xmlrpc_env_set_fault_formatted(
|
|
envP, XMLRPC_NETWORK_ERROR,
|
|
"HTTP error #%d occurred. libwww says %s", status, msg);
|
|
|
|
xmlrpc_strfree(msg);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
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_INTERNAL_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) {
|
|
/*----------------------------------------------------------------------------
|
|
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_INTERNAL_ERROR,
|
|
"Libwww (HTPostAnchor()) failed to start the "
|
|
"POST request.");
|
|
}
|
|
if (envP->fault_occurred)
|
|
rpcDestroy(rpcP);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
struct xmlrpc_client_transport_ops xmlrpc_libwww_transport_ops = {
|
|
&create,
|
|
&destroy,
|
|
&sendRequest,
|
|
&call,
|
|
&finishAsynch,
|
|
};
|