mirror of https://github.com/sipwise/sems.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
547 lines
16 KiB
547 lines
16 KiB
|
|
#include "XmlRpcClient.h"
|
|
|
|
#include "XmlRpcSocket.h"
|
|
#include "XmlRpc.h"
|
|
|
|
#include "base64.h" // For HTTP authentication encoding
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
|
|
using namespace XmlRpc;
|
|
using namespace std;
|
|
|
|
// Static data
|
|
const char XmlRpcClient::REQUEST_BEGIN[] =
|
|
"<?xml version=\"1.0\"?>\r\n"
|
|
"<methodCall><methodName>";
|
|
const char XmlRpcClient::REQUEST_END_METHODNAME[] = "</methodName>\r\n";
|
|
const char XmlRpcClient::PARAMS_TAG[] = "<params>";
|
|
const char XmlRpcClient::PARAMS_ETAG[] = "</params>";
|
|
const char XmlRpcClient::PARAM_TAG[] = "<param>";
|
|
const char XmlRpcClient::PARAM_ETAG[] = "</param>";
|
|
const char XmlRpcClient::REQUEST_END[] = "</methodCall>\r\n";
|
|
const char XmlRpcClient::METHODRESPONSE_TAG[] = "<methodResponse>";
|
|
const char XmlRpcClient::FAULT_TAG[] = "<fault>";
|
|
|
|
XmlRpcClient::XmlRpcClient(const char* host, int port, const char* uri/*=0*/)
|
|
{
|
|
XmlRpcUtil::log(1, "XmlRpcClient new client: host %s, port %d.", host, port);
|
|
|
|
_host = host;
|
|
_port = port;
|
|
if (uri && *uri)
|
|
_uri = uri;
|
|
else
|
|
_uri = "/RPC2";
|
|
_connectionState = NO_CONNECTION;
|
|
_executing = false;
|
|
_eof = false;
|
|
_ssl = false; _ssl_ssl = (SSL *) NULL;
|
|
|
|
// Default to keeping the connection open until an explicit close is done
|
|
setKeepOpen();
|
|
}
|
|
XmlRpcClient::XmlRpcClient(const char* host, int port, const char* uri, bool ssl)
|
|
{
|
|
XmlRpcUtil::log(1, "XmlRpcClient new client: host %s, port %d.", host, port);
|
|
|
|
_host = host;
|
|
_port = port;
|
|
if (uri && *uri)
|
|
_uri = uri;
|
|
else
|
|
_uri = "/RPC2";
|
|
_connectionState = NO_CONNECTION;
|
|
_executing = false;
|
|
_eof = false;
|
|
_ssl = ssl;
|
|
if (!_ssl) { _ssl_ssl = (SSL *) NULL; }
|
|
|
|
// Default to keeping the connection open until an explicit close is done
|
|
setKeepOpen();
|
|
}
|
|
|
|
|
|
XmlRpcClient::XmlRpcClient(const char* host, int port,
|
|
const char* login, const char* password, const char* uri/*=0*/)
|
|
{
|
|
XmlRpcUtil::log(1, "XmlRpcClient new client: host %s, port %d, login %s.", host, port, login);
|
|
|
|
_host = host;
|
|
_port = port;
|
|
if (uri)
|
|
_uri = uri;
|
|
else
|
|
_uri = "/RPC2";
|
|
|
|
_login = login;
|
|
_password = password;
|
|
|
|
_connectionState = NO_CONNECTION;
|
|
_executing = false;
|
|
_eof = false;
|
|
|
|
// Default to keeping the connection open until an explicit close is done
|
|
setKeepOpen();
|
|
}
|
|
|
|
XmlRpcClient::XmlRpcClient(const char* host, int port,
|
|
const char* login, const char* password,
|
|
const char* uri/*=0*/, bool ssl)
|
|
{
|
|
XmlRpcUtil::log(1, "XmlRpcClient new client: host %s, port %d, login %s.", host, port, login);
|
|
|
|
_host = host;
|
|
_port = port;
|
|
if (uri)
|
|
_uri = uri;
|
|
else
|
|
_uri = "/RPC2";
|
|
|
|
_login = login;
|
|
_password = password;
|
|
|
|
_connectionState = NO_CONNECTION;
|
|
_executing = false;
|
|
_eof = false;
|
|
_ssl = ssl;
|
|
if (!_ssl) { _ssl_ssl = (SSL *) NULL; }
|
|
|
|
_sendAttempts = 0;
|
|
_bytesWritten = 0;
|
|
_isFault = false;
|
|
_contentLength = 0;
|
|
|
|
// Default to keeping the connection open until an explicit close is done
|
|
setKeepOpen();
|
|
}
|
|
|
|
|
|
XmlRpcClient::~XmlRpcClient()
|
|
{
|
|
XmlRpcUtil::log(1, "XmlRpcClient dtor client: host %s, port %d.", _host.c_str(), _port);
|
|
if (_connectionState != NO_CONNECTION) close();
|
|
}
|
|
|
|
|
|
// Close the owned fd
|
|
void
|
|
XmlRpcClient::close()
|
|
{
|
|
XmlRpcUtil::log(4, "XmlRpcClient::close: fd %d.", getfd());
|
|
_connectionState = NO_CONNECTION;
|
|
_disp.exit();
|
|
_disp.removeSource(this);
|
|
if (_ssl) {
|
|
// Pre-socket shutdown
|
|
XmlRpcUtil::log(4, "XmlRpcClient::close: before SSL_shutdown");
|
|
SSL_shutdown(_ssl_ssl);
|
|
XmlRpcUtil::log(4, "XmlRpcClient::close: after SSL_shutdown");
|
|
}
|
|
|
|
/* potentially frees the `XmlRpcSource` itself via this */
|
|
XmlRpcSource::close();
|
|
}
|
|
|
|
|
|
// Clear the referenced flag even if exceptions or errors occur.
|
|
struct ClearFlagOnExit {
|
|
ClearFlagOnExit(bool& flag) : _flag(flag) {}
|
|
~ClearFlagOnExit() { _flag = false; }
|
|
bool& _flag;
|
|
};
|
|
|
|
// Execute the named procedure on the remote server.
|
|
// Params should be an array of the arguments for the method.
|
|
// Returns true if the request was sent and a result received (although the result
|
|
// might be a fault).
|
|
bool
|
|
XmlRpcClient::execute(const char* method, XmlRpcValue const& params, XmlRpcValue& result)
|
|
{
|
|
XmlRpcUtil::log(1, "XmlRpcClient::execute: method %s (_connectionState %d).", method, _connectionState);
|
|
|
|
// This is not a thread-safe operation, if you want to do multithreading, use separate
|
|
// clients for each thread. If you want to protect yourself from multiple threads
|
|
// accessing the same client, replace this code with a real mutex.
|
|
if (_executing)
|
|
return false;
|
|
|
|
_executing = true;
|
|
ClearFlagOnExit cf(_executing);
|
|
|
|
_sendAttempts = 0;
|
|
_isFault = false;
|
|
|
|
if ( ! setupConnection())
|
|
return false;
|
|
|
|
if ( ! generateRequest(method, params))
|
|
return false;
|
|
|
|
result.clear();
|
|
double msTime = -1.0; // Process until exit is called
|
|
_disp.work(msTime);
|
|
|
|
if (_connectionState != IDLE || ! parseResponse(result))
|
|
return false;
|
|
|
|
XmlRpcUtil::log(1, "XmlRpcClient::execute: method %s completed.", method);
|
|
_response = "";
|
|
return true;
|
|
}
|
|
|
|
// XmlRpcSource interface implementation
|
|
// Handle server responses. Called by the event dispatcher during execute.
|
|
unsigned
|
|
XmlRpcClient::handleEvent(unsigned eventType)
|
|
{
|
|
if (eventType == XmlRpcDispatch::Exception)
|
|
{
|
|
//if (XmlRpcSocket::nonFatalError())
|
|
// return (_connectionState == WRITE_REQUEST)
|
|
// ? XmlRpcDispatch::WritableEvent : XmlRpcDispatch::ReadableEvent;
|
|
|
|
if (_connectionState == WRITE_REQUEST && _bytesWritten == 0)
|
|
XmlRpcUtil::error("Error in XmlRpcClient::handleEvent: could not connect to server (%s).",
|
|
XmlRpcSocket::getErrorMsg().c_str());
|
|
else
|
|
XmlRpcUtil::error("Error in XmlRpcClient::handleEvent (state %d): %s.",
|
|
_connectionState, XmlRpcSocket::getErrorMsg().c_str());
|
|
return 0;
|
|
}
|
|
|
|
if (_connectionState == WRITE_REQUEST)
|
|
if ( ! writeRequest()) return 0;
|
|
|
|
if (_connectionState == READ_HEADER)
|
|
if ( ! readHeader()) return 0;
|
|
|
|
if (_connectionState == READ_RESPONSE)
|
|
if ( ! readResponse()) return 0;
|
|
|
|
// This should probably always ask for Exception events too
|
|
return (_connectionState == WRITE_REQUEST)
|
|
? XmlRpcDispatch::WritableEvent : XmlRpcDispatch::ReadableEvent;
|
|
}
|
|
|
|
|
|
// Create the socket connection to the server if necessary
|
|
bool
|
|
XmlRpcClient::setupConnection()
|
|
{
|
|
// If an error occurred last time through, or if the server closed the connection, close our end
|
|
if ((_connectionState != NO_CONNECTION && _connectionState != IDLE) || _eof)
|
|
close();
|
|
|
|
_eof = false;
|
|
if (_connectionState == NO_CONNECTION)
|
|
if (! doConnect())
|
|
return false;
|
|
|
|
// Prepare to write the request
|
|
_connectionState = WRITE_REQUEST;
|
|
_bytesWritten = 0;
|
|
|
|
// Notify the dispatcher to listen on this source (calls handleEvent when the socket is writable)
|
|
_disp.removeSource(this); // Make sure nothing is left over
|
|
_disp.addSource(this, XmlRpcDispatch::WritableEvent | XmlRpcDispatch::Exception);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// Connect to the xmlrpc server
|
|
bool
|
|
XmlRpcClient::doConnect()
|
|
{
|
|
int fd = XmlRpcSocket::socket();
|
|
if (fd < 0)
|
|
{
|
|
XmlRpcUtil::error("Error in XmlRpcClient::doConnect: Could not create socket (%s).", XmlRpcSocket::getErrorMsg().c_str());
|
|
return false;
|
|
}
|
|
|
|
XmlRpcUtil::log(3, "XmlRpcClient::doConnect: fd %d.", fd);
|
|
this->setfd(fd);
|
|
|
|
// Don't block on connect/reads/writes
|
|
if ( ! XmlRpcSocket::setNonBlocking(fd))
|
|
{
|
|
this->close();
|
|
XmlRpcUtil::error("Error in XmlRpcClient::doConnect: Could not set socket to non-blocking IO mode (%s).", XmlRpcSocket::getErrorMsg().c_str());
|
|
return false;
|
|
}
|
|
|
|
if ( ! XmlRpcSocket::connect(fd, _host, _port))
|
|
{
|
|
this->close();
|
|
XmlRpcUtil::error("Error in XmlRpcClient::doConnect: Could not connect to server (%s).", XmlRpcSocket::getErrorMsg().c_str());
|
|
return false;
|
|
}
|
|
|
|
// Perform SSL if needed
|
|
if (_ssl) {
|
|
SSLeay_add_ssl_algorithms();
|
|
_ssl_meth = SSLv23_client_method();
|
|
SSL_load_error_strings();
|
|
_ssl_ctx = SSL_CTX_new (_ssl_meth);
|
|
_ssl_ssl = SSL_new (_ssl_ctx);
|
|
SSL_set_fd (_ssl_ssl, fd);
|
|
/* int err = */ SSL_connect (_ssl_ssl);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Encode the request to call the specified method with the specified parameters into xml
|
|
bool
|
|
XmlRpcClient::generateRequest(const char* methodName, XmlRpcValue const& params)
|
|
{
|
|
std::string body = REQUEST_BEGIN;
|
|
body += methodName;
|
|
body += REQUEST_END_METHODNAME;
|
|
|
|
// If params is an array, each element is a separate parameter
|
|
if (params.valid()) {
|
|
body += PARAMS_TAG;
|
|
if (params.getType() == XmlRpcValue::TypeArray)
|
|
{
|
|
for (size_t i=0; i<params.size(); ++i) {
|
|
body += PARAM_TAG;
|
|
body += params[i].toXml();
|
|
body += PARAM_ETAG;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
body += PARAM_TAG;
|
|
body += params.toXml();
|
|
body += PARAM_ETAG;
|
|
}
|
|
|
|
body += PARAMS_ETAG;
|
|
}
|
|
body += REQUEST_END;
|
|
|
|
std::string header = generateHeader(body);
|
|
XmlRpcUtil::log(4, "XmlRpcClient::generateRequest: header is %zu bytes, content-length is %zu.",
|
|
header.length(), body.length());
|
|
|
|
_request = header + body;
|
|
return true;
|
|
}
|
|
|
|
// Prepend http headers
|
|
std::string
|
|
XmlRpcClient::generateHeader(std::string const& body)
|
|
{
|
|
std::string header =
|
|
"POST " + _uri + " HTTP/1.1\r\n"
|
|
"User-Agent: ";
|
|
header += XMLRPC_VERSION;
|
|
header += "\r\nHost: ";
|
|
header += _host;
|
|
|
|
char buff[40];
|
|
sprintf(buff,":%d\r\n", _port);
|
|
|
|
header += buff;
|
|
|
|
if (_login.length() != 0)
|
|
{
|
|
// convert to base64
|
|
std::vector<char> base64data;
|
|
int iostatus = 0;
|
|
base64<char> encoder;
|
|
std::back_insert_iterator<std::vector<char> > ins =
|
|
std::back_inserter(base64data);
|
|
|
|
std::string authBuf = _login + ":" + _password;
|
|
|
|
encoder.put(authBuf.begin(), authBuf.end(), ins, iostatus,
|
|
base64<>::crlf());
|
|
|
|
header += "Authorization: Basic ";
|
|
std::string authEnc(base64data.begin(), base64data.end());
|
|
// handle pesky linefeed characters
|
|
string::size_type lf;
|
|
while ( (lf = authEnc.find("\r")) != string::npos ) {
|
|
authEnc.erase(lf, 1);
|
|
}
|
|
while ( (lf = authEnc.find("\n")) != string::npos ) {
|
|
authEnc.erase(lf, 1);
|
|
}
|
|
header += authEnc;
|
|
header += "\r\n";
|
|
}
|
|
|
|
header += "Content-Type: text/xml\r\nContent-length: ";
|
|
|
|
sprintf(buff,"%zd\r\n\r\n", body.size());
|
|
|
|
return header + buff;
|
|
}
|
|
|
|
bool
|
|
XmlRpcClient::writeRequest()
|
|
{
|
|
if (_bytesWritten == 0)
|
|
XmlRpcUtil::log(5, "XmlRpcClient::writeRequest (attempt %d):\n%s\n", _sendAttempts+1, _request.c_str());
|
|
|
|
// Try to write the request
|
|
if ( ! XmlRpcSocket::nbWrite(this->getfd(), _request, _bytesWritten, _ssl_ssl)) {
|
|
XmlRpcUtil::error("Error in XmlRpcClient::writeRequest: write error (%s).",XmlRpcSocket::getErrorMsg().c_str());
|
|
return false;
|
|
}
|
|
|
|
XmlRpcUtil::log(3, "XmlRpcClient::writeRequest: wrote %zd of %zu bytes.", _bytesWritten, _request.length());
|
|
|
|
// Wait for the result
|
|
if (_bytesWritten == _request.length()) {
|
|
_header = "";
|
|
_response = "";
|
|
_connectionState = READ_HEADER;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
// Read the header from the response
|
|
bool
|
|
XmlRpcClient::readHeader()
|
|
{
|
|
// Read available data
|
|
if ( ! XmlRpcSocket::nbRead(this->getfd(), _header, &_eof, _ssl_ssl) ||
|
|
(_eof && _header.length() == 0)) {
|
|
|
|
// If we haven't read any data yet and this is a keep-alive connection, the server may
|
|
// have timed out, so we try one more time.
|
|
if (getKeepOpen() && _header.length() == 0 && _sendAttempts++ == 0) {
|
|
XmlRpcUtil::log(4, "XmlRpcClient::readHeader: re-trying connection");
|
|
XmlRpcSource::close();
|
|
_connectionState = NO_CONNECTION;
|
|
_eof = false;
|
|
return setupConnection();
|
|
}
|
|
|
|
XmlRpcUtil::error("Error in XmlRpcClient::readHeader: error while reading header (%s) on fd %d.",
|
|
XmlRpcSocket::getErrorMsg().c_str(), getfd());
|
|
return false;
|
|
}
|
|
|
|
XmlRpcUtil::log(4, "XmlRpcClient::readHeader: client has read %zu bytes", _header.length());
|
|
|
|
char *hp = (char*)_header.c_str(); // Start of header
|
|
char *ep = hp + _header.length(); // End of string
|
|
char *bp = 0; // Start of body
|
|
char *lp = 0; // Start of content-length value
|
|
|
|
for (char *cp = hp; (bp == 0) && (cp < ep); ++cp) {
|
|
if ((ep - cp > 16) && (strncasecmp(cp, "Content-length: ", 16) == 0))
|
|
lp = cp + 16;
|
|
else if ((ep - cp > 4) && (strncmp(cp, "\r\n\r\n", 4) == 0))
|
|
bp = cp + 4;
|
|
else if ((ep - cp > 2) && (strncmp(cp, "\n\n", 2) == 0))
|
|
bp = cp + 2;
|
|
}
|
|
|
|
// If we haven't gotten the entire header yet, return (keep reading)
|
|
if (bp == 0) {
|
|
if (_eof) // EOF in the middle of a response is an error
|
|
{
|
|
XmlRpcUtil::error("Error in XmlRpcClient::readHeader: EOF while reading header");
|
|
return false; // Close the connection
|
|
}
|
|
|
|
return true; // Keep reading
|
|
}
|
|
|
|
// Decode content length
|
|
if (lp == 0) {
|
|
XmlRpcUtil::error("Error XmlRpcClient::readHeader: No Content-length specified");
|
|
return false; // We could try to figure it out by parsing as we read, but for now...
|
|
}
|
|
|
|
_contentLength = atoi(lp);
|
|
if (_contentLength <= 0) {
|
|
XmlRpcUtil::error("Error in XmlRpcClient::readHeader: Invalid Content-length specified (%d).", _contentLength);
|
|
return false;
|
|
}
|
|
|
|
XmlRpcUtil::log(4, "client read content length: %d", _contentLength);
|
|
|
|
// Otherwise copy non-header data to response buffer and set state to read response.
|
|
_response = bp;
|
|
_header = ""; // should parse out any interesting bits from the header (connection, etc)...
|
|
_connectionState = READ_RESPONSE;
|
|
return true; // Continue monitoring this source
|
|
}
|
|
|
|
|
|
bool
|
|
XmlRpcClient::readResponse()
|
|
{
|
|
// If we dont have the entire response yet, read available data
|
|
if (int(_response.length()) < _contentLength) {
|
|
if ( ! XmlRpcSocket::nbRead(this->getfd(), _response, &_eof, _ssl_ssl)) {
|
|
XmlRpcUtil::error("Error in XmlRpcClient::readResponse: read error (%s).",XmlRpcSocket::getErrorMsg().c_str());
|
|
return false;
|
|
}
|
|
|
|
// If we haven't gotten the entire _response yet, return (keep reading)
|
|
if (int(_response.length()) < _contentLength) {
|
|
if (_eof) {
|
|
XmlRpcUtil::error("Error in XmlRpcClient::readResponse: EOF while reading response");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Otherwise, parse and return the result
|
|
XmlRpcUtil::log(3, "XmlRpcClient::readResponse (read %zu bytes)", static_cast<size_t>(_response.length()));
|
|
XmlRpcUtil::log(5, "response:\n%s", _response.c_str());
|
|
|
|
_connectionState = IDLE;
|
|
|
|
return false; // Stop monitoring this source (causes return from work)
|
|
}
|
|
|
|
|
|
// Convert the response xml into a result value
|
|
bool
|
|
XmlRpcClient::parseResponse(XmlRpcValue& result)
|
|
{
|
|
// Parse response xml into result
|
|
size_t offset = 0;
|
|
if ( ! XmlRpcUtil::findTag(METHODRESPONSE_TAG,_response,&offset)) {
|
|
XmlRpcUtil::error("Error in XmlRpcClient::parseResponse: Invalid response - no methodResponse. Response:\n%s", _response.c_str());
|
|
return false;
|
|
}
|
|
|
|
// Expect either <params><param>... or <fault>...
|
|
if ((XmlRpcUtil::nextTagIs(PARAMS_TAG,_response,&offset) &&
|
|
XmlRpcUtil::nextTagIs(PARAM_TAG,_response,&offset)) ||
|
|
(XmlRpcUtil::nextTagIs(FAULT_TAG,_response,&offset) && setFault()))
|
|
{
|
|
if ( ! result.fromXml(_response, &offset)) {
|
|
XmlRpcUtil::error("Error in XmlRpcClient::parseResponse: Invalid response value. Response:\n%s", _response.c_str());
|
|
_response = "";
|
|
return false;
|
|
}
|
|
} else {
|
|
XmlRpcUtil::error("Error in XmlRpcClient::parseResponse: Invalid response - no param or fault tag. Response:\n%s", _response.c_str());
|
|
_response = "";
|
|
return false;
|
|
}
|
|
|
|
_response = "";
|
|
return result.valid();
|
|
}
|
|
|