initial load of upstream version 1.06.32
[xmlrpc-c] / src / cpp / client.cpp
diff --git a/src/cpp/client.cpp b/src/cpp/client.cpp
new file mode 100644 (file)
index 0000000..50d8695
--- /dev/null
@@ -0,0 +1,962 @@
+/*=============================================================================
+                                client.cpp
+===============================================================================
+  This is the C++ XML-RPC client library for Xmlrpc-c.
+
+  Note that unlike most of Xmlprc-c's C++ API, this is _not_ based on the
+  C client library.  This code is independent of the C client library, and
+  is based directly on the client XML transport libraries (with a little
+  help from internal C utility libraries).
+=============================================================================*/
+
+#include <stdlib.h>
+#include <cassert>
+#include <string>
+#include <vector>
+
+#include "xmlrpc-c/girerr.hpp"
+using girerr::error;
+using girerr::throwf;
+#include "xmlrpc-c/girmem.hpp"
+using girmem::autoObjectPtr;
+using girmem::autoObject;
+#include "env_wrap.hpp"
+#include "xmlrpc-c/base.h"
+#include "xmlrpc-c/client.h"
+#include "xmlrpc-c/transport.h"
+#include "xmlrpc-c/base.hpp"
+#include "xmlrpc-c/xml.hpp"
+#include "xmlrpc-c/client.hpp"
+#include "transport_config.h"
+
+using namespace std;
+using namespace xmlrpc_c;
+
+
+namespace {
+
+void
+throwIfError(env_wrap const& env) {
+
+    if (env.env_c.fault_occurred)
+        throw(error(env.env_c.fault_string));
+}
+
+
+
+class memblockStringWrapper {
+
+public:    
+    memblockStringWrapper(string const value) {
+
+        env_wrap env;
+
+        this->memblockP = XMLRPC_MEMBLOCK_NEW(char, &env.env_c, 0);
+        throwIfError(env);
+
+        XMLRPC_MEMBLOCK_APPEND(char, &env.env_c, this->memblockP,
+                               value.c_str(), value.size());
+        throwIfError(env);
+    }
+    
+    memblockStringWrapper(xmlrpc_mem_block * const memblockP) :
+        memblockP(memblockP) {};
+
+    ~memblockStringWrapper() {
+        XMLRPC_MEMBLOCK_FREE(char, this->memblockP);
+    }
+
+    xmlrpc_mem_block * memblockP;
+};
+
+} // namespace
+
+namespace xmlrpc_c {
+
+carriageParm::carriageParm() {}
+
+
+
+carriageParm::~carriageParm() {}
+
+
+
+carriageParmPtr::carriageParmPtr() {
+    // Base class constructor will construct pointer that points to nothing
+}
+
+
+
+carriageParmPtr::carriageParmPtr(
+    carriageParm * const carriageParmP) {
+    this->point(carriageParmP);
+}
+
+
+
+carriageParm *
+carriageParmPtr::operator->() const {
+
+    autoObject * const p(this->objectP);
+    return dynamic_cast<carriageParm *>(p);
+}
+
+
+
+carriageParm *
+carriageParmPtr::get() const {
+    return dynamic_cast<carriageParm *>(objectP);
+}
+
+
+
+carriageParm_http0::carriageParm_http0() :
+    c_serverInfoP(NULL) {}
+
+
+
+carriageParm_http0::carriageParm_http0(string const serverUrl) {
+    this->c_serverInfoP = NULL;
+
+    this->instantiate(serverUrl);
+}
+
+
+
+carriageParm_http0::~carriageParm_http0() {
+
+    if (this->c_serverInfoP)
+        xmlrpc_server_info_free(this->c_serverInfoP);
+}
+
+
+
+void
+carriageParm_http0::instantiate(string const serverUrl) {
+
+    if (c_serverInfoP)
+        throw(error("object already instantiated"));
+    
+    env_wrap env;
+
+    this->c_serverInfoP =
+        xmlrpc_server_info_new(&env.env_c, serverUrl.c_str());
+    throwIfError(env);
+}
+
+
+
+void
+carriageParm_http0::setBasicAuth(string const username,
+                                 string const password) {
+
+    if (!c_serverInfoP)
+        throw(error("object not instantiated"));
+    
+    env_wrap env;
+
+    xmlrpc_server_info_set_basic_auth(
+        &env.env_c, this->c_serverInfoP, username.c_str(), password.c_str());
+    throwIfError(env);
+}
+
+
+
+carriageParm_http0Ptr::carriageParm_http0Ptr() {
+    // Base class constructor will construct pointer that points to nothing
+}
+
+
+
+carriageParm_http0Ptr::carriageParm_http0Ptr(
+    carriageParm_http0 * const carriageParmP) {
+    this->point(carriageParmP);
+}
+
+
+
+carriageParm_http0 *
+carriageParm_http0Ptr::operator->() const {
+
+    autoObject * const p(this->objectP);
+    return dynamic_cast<carriageParm_http0 *>(p);
+}
+
+
+
+xmlTransaction::xmlTransaction() {}
+
+
+
+void
+xmlTransaction::finish(string const& responseXml) const {
+
+    xml::trace("XML-RPC RESPONSE", responseXml);
+}
+
+
+
+void
+xmlTransaction::finishErr(error const&) const {
+
+}
+
+
+
+xmlTransactionPtr::xmlTransactionPtr() {}
+
+
+
+xmlTransaction *
+xmlTransactionPtr::operator->() const {
+    autoObject * const p(this->objectP);
+    return dynamic_cast<xmlTransaction *>(p);
+}
+
+
+
+struct xmlTranCtl {
+/*----------------------------------------------------------------------------
+   This contains information needed to conduct a transaction.  You
+   construct it as you start the transaction and destroy it after the
+   work is done.  You need this only for an asynchronous one, because
+   where the user starts and finishes the RPC in the same
+   libxmlrpc_client call, you can just keep this information in
+   various stack variables, and it's faster and easier to understand
+   that way.
+
+   The C transport is designed to take a xmlrpc_call_info argument for
+   similar stuff needed by the the C client object.  But it's really
+   opaque to the transport, so we just let xmlTranCtl masquerade as
+   xmlprc_call_info in our call to the C transport.
+-----------------------------------------------------------------------------*/
+    xmlTranCtl(xmlTransactionPtr const& xmlTranP,
+               string            const& callXml) :
+
+        xmlTranP(xmlTranP) {
+
+        env_wrap env;
+
+        this->callXmlP = XMLRPC_MEMBLOCK_NEW(char, &env.env_c, 0);
+        throwIfError(env);
+
+        XMLRPC_MEMBLOCK_APPEND(char, &env.env_c, this->callXmlP,
+                               callXml.c_str(), callXml.size());
+        throwIfError(env);
+    }
+    
+    ~xmlTranCtl() {
+        XMLRPC_MEMBLOCK_FREE(char, this->callXmlP);
+    }
+
+    xmlTransactionPtr const xmlTranP;
+        // The transaction we're controlling.  Most notable use of this is
+        // that this object we inform when the transaction is done.  This
+        // is where the response XML and other transaction results go.
+
+    xmlrpc_mem_block * callXmlP;
+        // The XML of the call.  This is what the transport transports.
+};
+
+
+
+clientXmlTransport::~clientXmlTransport() {}
+
+
+
+void
+clientXmlTransport::start(carriageParm *    const  carriageParmP,
+                          string            const& callXml,
+                          xmlTransactionPtr const& xmlTranP) {
+    
+    string responseXml;
+
+    this->call(carriageParmP, callXml, &responseXml);
+
+    xmlTranP->finish(responseXml);
+}
+
+
+
+void
+clientXmlTransport::finishAsync(xmlrpc_c::timeout const timeout) {
+    if (timeout.finite == timeout.finite)
+        throw(error("This class does not have finishAsync()"));
+}
+
+
+
+void
+clientXmlTransport::asyncComplete(
+    struct xmlrpc_call_info * const callInfoP,
+    xmlrpc_mem_block *        const responseXmlMP,
+    xmlrpc_env                const transportEnv) {
+
+    xmlTranCtl * const xmlTranCtlP = reinterpret_cast<xmlTranCtl *>(callInfoP);
+
+    try {
+        if (transportEnv.fault_occurred) {
+            xmlTranCtlP->xmlTranP->finishErr(error(transportEnv.fault_string));
+        } else {
+            string const responseXml(
+                XMLRPC_MEMBLOCK_CONTENTS(char, responseXmlMP),
+                XMLRPC_MEMBLOCK_SIZE(char, responseXmlMP));
+            xmlTranCtlP->xmlTranP->finish(responseXml);
+        }
+    } catch(error) {
+        /* We can't throw an error back to C code, and the async_complete
+           interface does not provide for failure, so we define ->finish()
+           as not being capable of throwing an error.
+        */
+        assert(false);
+    }
+    delete(xmlTranCtlP);
+
+    /* Ordinarily, *xmlTranCtlP is the last reference to
+       xmlTranCtlP->xmlTranP, so that will get destroyed too.  But
+       ->finish() could conceivably create a new reference to
+       xmlTranCtlP->xmlTranP, and then it would keep living.
+    */
+}
+
+
+
+clientXmlTransportPtr::clientXmlTransportPtr() {
+    // Base class constructor will construct pointer that points to nothing
+}
+
+
+
+clientXmlTransportPtr::clientXmlTransportPtr(
+    clientXmlTransport * const transportP) {
+    this->point(transportP);
+}
+
+
+
+clientXmlTransport *
+clientXmlTransportPtr::get() const {
+    return dynamic_cast<clientXmlTransport *>(objectP);
+}
+
+
+
+clientXmlTransport *
+clientXmlTransportPtr::operator->() const {
+
+    autoObject * const p(this->objectP);
+    return dynamic_cast<clientXmlTransport *>(p);
+}
+
+
+
+clientXmlTransport_http::~clientXmlTransport_http() {}
+
+
+
+void
+clientXmlTransport_http::call(
+    carriageParm * const  carriageParmP,
+    string         const& callXml,
+    string *       const  responseXmlP) {
+
+    carriageParm_http0 * const carriageParmHttpP =
+        dynamic_cast<carriageParm_http0 *>(carriageParmP);
+
+    if (carriageParmHttpP == NULL)
+        throw(error("HTTP client XML transport called with carriage "
+                    "parameter object not of class carriageParm_http"));
+
+    memblockStringWrapper callXmlM(callXml);
+
+    xmlrpc_mem_block * responseXmlMP;
+
+    env_wrap env;
+
+    this->c_transportOpsP->call(&env.env_c,
+                                this->c_transportP,
+                                carriageParmHttpP->c_serverInfoP,
+                                callXmlM.memblockP,
+                                &responseXmlMP);
+
+    throwIfError(env);
+
+    memblockStringWrapper responseHolder(responseXmlMP);
+        // Makes responseXmlMP get freed at end of scope
+    
+    *responseXmlP = string(XMLRPC_MEMBLOCK_CONTENTS(char, responseXmlMP),
+                           XMLRPC_MEMBLOCK_SIZE(char, responseXmlMP));
+}
+
+
+
+void
+clientXmlTransport_http::start(
+    carriageParm *    const  carriageParmP,
+    string            const& callXml,
+    xmlTransactionPtr const& xmlTranP) {
+
+    env_wrap env;
+
+    carriageParm_http0 * const carriageParmHttpP =
+        dynamic_cast<carriageParm_http0 *>(carriageParmP);
+
+    if (carriageParmHttpP == NULL)
+        throw(error("HTTP client XML transport called with carriage "
+                    "parameter object not of type carriageParm_http"));
+
+    xmlTranCtl * const tranCtlP(new xmlTranCtl(xmlTranP, callXml));
+
+    try {
+        this->c_transportOpsP->send_request(
+            &env.env_c,
+            this->c_transportP,
+            carriageParmHttpP->c_serverInfoP,
+            tranCtlP->callXmlP,
+            &this->asyncComplete,
+            reinterpret_cast<xmlrpc_call_info *>(tranCtlP));
+
+        throwIfError(env);
+    } catch (...) {
+        delete tranCtlP;
+        throw;
+    }
+}
+
+
+
+void
+clientXmlTransport_http::finishAsync(xmlrpc_c::timeout const timeout) {
+
+    xmlrpc_timeoutType const c_timeoutType(
+        timeout.finite ? timeout_yes : timeout_no);
+    xmlrpc_timeout const c_timeout(timeout.duration);
+
+    this->c_transportOpsP->finish_asynch(
+        this->c_transportP, c_timeoutType, c_timeout);
+}
+
+
+bool const haveCurl(
+#if MUST_BUILD_CURL_CLIENT
+true
+#else
+false
+#endif
+);
+
+bool const haveLibwww(
+#if MUST_BUILD_LIBWWW_CLIENT
+true
+#else
+false
+#endif
+);
+
+bool const haveWininet(
+#if MUST_BUILD_WININET_CLIENT
+true
+#else
+false
+#endif
+);
+
+
+
+vector<string>
+clientXmlTransport_http::availableTypes() {
+
+    vector<string> retval;
+
+    if (haveCurl)
+        retval.push_back("curl");
+
+    if (haveLibwww)
+        retval.push_back("libwww");
+
+    if (haveWininet)
+        retval.push_back("wininet");
+
+    return retval;
+}
+
+
+
+clientXmlTransportPtr
+clientXmlTransport_http::create() {
+/*----------------------------------------------------------------------------
+  Make an HTTP Client XML transport of any kind (Caller doesn't care).
+
+  Caller can find out what kind he got by trying dynamic casts.
+
+  Caller can use a carriageParm_http0 with the transport.
+-----------------------------------------------------------------------------*/
+    if (haveCurl)
+        return clientXmlTransportPtr(new clientXmlTransport_curl());
+    else if (haveLibwww)
+        return clientXmlTransportPtr(new clientXmlTransport_libwww());
+    else if (haveWininet)
+        return clientXmlTransportPtr(new clientXmlTransport_wininet());
+    else
+        throwf("This XML-RPC client library contains no HTTP XML transports");
+}
+
+
+
+clientTransaction::clientTransaction() {}
+
+
+
+clientTransactionPtr::clientTransactionPtr() {}
+
+
+
+clientTransactionPtr::~clientTransactionPtr() {}
+
+
+
+clientTransaction *
+clientTransactionPtr::operator->() const {
+    autoObject * const p(this->objectP);
+    return dynamic_cast<clientTransaction *>(p);
+}
+
+
+
+client::~client() {}
+
+
+
+void
+client::start(carriageParm *       const  carriageParmP,
+              string               const& methodName,
+              paramList            const& paramList,
+              clientTransactionPtr const& tranP) {
+/*----------------------------------------------------------------------------
+   Start an RPC, wait for it to complete, and finish it.
+
+   Usually, a derived class overrides this with something that does
+   not wait for the RPC to complete, but rather arranges for something
+   to finish the RPC later when the RPC does complete.
+-----------------------------------------------------------------------------*/
+    rpcOutcome outcome;
+
+    this->call(carriageParmP, methodName, paramList, &outcome);
+
+    tranP->finish(outcome);
+}
+
+
+
+clientPtr::clientPtr() {
+    // Base class constructor will construct pointer that points to nothing
+}
+
+
+
+clientPtr::clientPtr(
+    client * const clientP) {
+    this->point(clientP);
+}
+
+
+
+client *
+clientPtr::operator->() const {
+
+    autoObject * const p(this->objectP);
+    return dynamic_cast<client *>(p);
+}
+
+
+
+client *
+clientPtr::get() const {
+    return dynamic_cast<client *>(objectP);
+}
+
+
+
+client_xml::client_xml(clientXmlTransport * const transportP) :
+    transportP(transportP) {}
+
+
+
+client_xml::client_xml(clientXmlTransportPtr const transportPtr) {
+
+    this->transportPtr = transportPtr;
+    this->transportP   = transportPtr.get();
+}
+     
+
+
+void
+client_xml::call(carriageParm * const  carriageParmP,
+                 string         const& methodName,
+                 paramList      const& paramList,
+                 rpcOutcome *   const  outcomeP) {
+
+    string callXml;
+    string responseXml;
+
+    xml::generateCall(methodName, paramList, &callXml);
+    
+    xml::trace("XML-RPC CALL", callXml);
+
+    try {
+        this->transportP->call(carriageParmP, callXml, &responseXml);
+    } catch (error const& error) {
+        throwf("Unable to transport XML to server and "
+               "get XML response back.  %s", error.what());
+    }
+    xml::trace("XML-RPC RESPONSE", responseXml);
+        
+    try {
+        xml::parseResponse(responseXml, outcomeP);
+    } catch (error const& error) {
+        throwf("Response XML from server is not valid XML-RPC response.  %s",
+               error.what());
+    }
+}
+
+
+void
+client_xml::start(carriageParm *       const  carriageParmP,
+                  string               const& methodName,
+                  paramList            const& paramList,
+                  clientTransactionPtr const& tranP) {
+
+    string callXml;
+
+    xml::generateCall(methodName, paramList, &callXml);
+    
+    xml::trace("XML-RPC CALL", callXml);
+
+    xmlTransaction_clientPtr const xmlTranP(tranP);
+
+    this->transportP->start(carriageParmP, callXml, xmlTranP);
+}
+
+
+void
+client_xml::finishAsync(xmlrpc_c::timeout const timeout) {
+
+    transportP->finishAsync(timeout);
+}
+
+
+
+serverAccessor::serverAccessor(clientPtr       const clientP,
+                               carriageParmPtr const carriageParmP) :
+
+    clientP(clientP), carriageParmP(carriageParmP) {};
+
+
+
+void
+serverAccessor::call(std::string            const& methodName,
+                     xmlrpc_c::paramList    const& paramList,
+                     xmlrpc_c::rpcOutcome * const  outcomeP) const {
+
+    this->clientP->call(this->carriageParmP.get(),
+                        methodName,
+                        paramList,
+                        outcomeP);
+}
+
+
+
+serverAccessorPtr::serverAccessorPtr() {
+    // Base class constructor will construct pointer that points to nothing
+}
+
+
+
+serverAccessorPtr::serverAccessorPtr(
+    serverAccessor * const serverAccessorParmP) {
+    this->point(serverAccessorParmP);
+}
+
+
+
+serverAccessor *
+serverAccessorPtr::operator->() const {
+
+    autoObject * const p(this->objectP);
+    return dynamic_cast<serverAccessor *>(p);
+}
+
+
+
+serverAccessor *
+serverAccessorPtr::get() const {
+    return dynamic_cast<serverAccessor *>(objectP);
+}
+
+
+
+connection::connection(client *       const clientP,
+                       carriageParm * const carriageParmP) :
+    clientP(clientP), carriageParmP(carriageParmP) {}
+
+
+
+connection::~connection() {}
+
+
+
+rpc::rpc(string              const  methodName,
+         xmlrpc_c::paramList const& paramList) {
+    
+    this->state      = STATE_UNFINISHED;
+    this->methodName = methodName;
+    this->paramList  = paramList;
+}
+
+
+
+rpc::~rpc() {
+
+    if (this->state == STATE_ERROR)
+        delete(this->errorP);
+}
+
+
+
+void
+rpc::call(client       * const clientP,
+          carriageParm * const carriageParmP) {
+
+    if (this->state != STATE_UNFINISHED)
+        throw(error("Attempt to execute an RPC that has already been "
+                    "executed"));
+
+    clientP->call(carriageParmP,
+                  this->methodName,
+                  this->paramList,
+                  &this->outcome);
+
+    this->state = outcome.succeeded() ? STATE_SUCCEEDED : STATE_FAILED;
+}
+
+
+
+void
+rpc::call(connection const& connection) {
+
+    this->call(connection.clientP, connection.carriageParmP);
+
+}
+
+
+void
+rpc::start(client       * const clientP,
+           carriageParm * const carriageParmP) {
+    
+    if (this->state != STATE_UNFINISHED)
+        throw(error("Attempt to execute an RPC that has already been "
+                    "executed"));
+
+    clientP->start(carriageParmP,
+                   this->methodName,
+                   this->paramList,
+                   rpcPtr(this));
+}
+
+
+void
+rpc::start(xmlrpc_c::connection const& connection) {
+    
+    this->start(connection.clientP, connection.carriageParmP);
+}
+
+
+
+void
+rpc::finish(rpcOutcome const& outcome) {
+
+    this->state = outcome.succeeded() ? STATE_SUCCEEDED : STATE_FAILED;
+
+    this->outcome = outcome;
+
+    this->notifyComplete();
+}
+
+
+
+void
+rpc::finishErr(error const& error) {
+
+    this->state = STATE_ERROR;
+    this->errorP = new girerr::error(error);
+    this->notifyComplete();
+}
+
+
+
+void
+rpc::notifyComplete() {
+/*----------------------------------------------------------------------------
+   Anyone who does RPCs asynchronously and doesn't use polling will
+   want to make his own class derived from 'rpc' and override this
+   with a notifyFinish() that does something.
+
+   Typically, notifyFinish() will queue the RPC so some other thread
+   will deal with the fact that the RPC is finished.
+
+
+   In the absence of the aforementioned queueing, the RPC becomes
+   unreferenced as soon as our Caller releases his reference, so the
+   RPC gets destroyed when we return.
+-----------------------------------------------------------------------------*/
+
+}
+
+    
+
+value
+rpc::getResult() const {
+
+    switch (this->state) {
+    case STATE_UNFINISHED:
+        throw(error("Attempt to get result of RPC that is not finished."));
+        break;
+    case STATE_ERROR:
+        throw(*this->errorP);
+        break;
+    case STATE_FAILED:
+        throw(error("RPC response indicates failure.  " +
+                    this->outcome.getFault().getDescription()));
+        break;
+    case STATE_SUCCEEDED: {
+        // All normal
+    }
+    }
+
+    return this->outcome.getResult();
+}
+
+
+
+
+fault
+rpc::getFault() const {
+
+    switch (this->state) {
+    case STATE_UNFINISHED:
+        throw(error("Attempt to get fault from RPC that is not finished"));
+        break;
+    case STATE_ERROR:
+        throw(*this->errorP);
+        break;
+    case STATE_SUCCEEDED:
+        throw(error("Attempt to get fault from an RPC that succeeded"));
+        break;
+    case STATE_FAILED: {
+        // All normal
+    }
+    }
+
+    return this->outcome.getFault();
+}
+
+
+
+bool
+rpc::isFinished() const {
+    return (this->state != STATE_UNFINISHED);
+}
+
+
+
+bool
+rpc::isSuccessful() const {
+    return (this->state == STATE_SUCCEEDED);
+}
+
+
+
+rpcPtr::rpcPtr() {}
+
+
+
+rpcPtr::rpcPtr(rpc * const rpcP) {
+    this->point(rpcP);
+}
+
+
+
+rpcPtr::rpcPtr(string              const  methodName,
+               xmlrpc_c::paramList const& paramList) {
+
+    this->point(new rpc(methodName, paramList));
+}
+
+
+
+rpc *
+rpcPtr::operator->() const {
+
+    autoObject * const p(this->objectP);
+    return dynamic_cast<rpc *>(p);
+}
+
+
+
+xmlTransaction_client::xmlTransaction_client(
+    clientTransactionPtr const& tranP) :
+    tranP(tranP) {}
+
+
+
+void
+xmlTransaction_client::finish(string const& responseXml) const {
+
+    xml::trace("XML-RPC RESPONSE", responseXml);
+
+    try {
+        rpcOutcome outcome;
+    
+        xml::parseResponse(responseXml, &outcome);
+
+        this->tranP->finish(outcome);
+    } catch (error const& error) {
+        this->tranP->finishErr(error);
+    }
+}
+
+
+
+void
+xmlTransaction_client::finishErr(error const& error) const {
+
+    this->tranP->finishErr(error);
+}
+
+
+
+xmlTransaction_clientPtr::xmlTransaction_clientPtr() {}
+
+
+
+xmlTransaction_clientPtr::xmlTransaction_clientPtr(
+    clientTransactionPtr const& tranP) {
+
+    this->point(new xmlTransaction_client(tranP));
+}
+
+
+
+xmlTransaction_client *
+xmlTransaction_clientPtr::operator->() const {
+    autoObject * const p(this->objectP);
+    return dynamic_cast<xmlTransaction_client *>(p);
+}
+
+
+
+} // namespace