OverSim
XmlRpcClient.cc
Go to the documentation of this file.
1 
7 #include "XmlRpcClient.h"
8 
9 #include "XmlRpcSocket.h"
10 #include "XmlRpc.h"
11 
12 #include "base64.h" // For HTTP authentication encoding
13 
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string>
17 #include <string.h>
18 
19 using namespace XmlRpc;
20 using namespace std;
21 
22 // Static data
23 const char XmlRpcClient::REQUEST_BEGIN[] =
24  "<?xml version=\"1.0\"?>\r\n"
25  "<methodCall><methodName>";
26 const char XmlRpcClient::REQUEST_END_METHODNAME[] = "</methodName>\r\n";
27 const char XmlRpcClient::PARAMS_TAG[] = "<params>";
28 const char XmlRpcClient::PARAMS_ETAG[] = "</params>";
29 const char XmlRpcClient::PARAM_TAG[] = "<param>";
30 const char XmlRpcClient::PARAM_ETAG[] = "</param>";
31 const char XmlRpcClient::REQUEST_END[] = "</methodCall>\r\n";
32 const char XmlRpcClient::METHODRESPONSE_TAG[] = "<methodResponse>";
33 const char XmlRpcClient::FAULT_TAG[] = "<fault>";
34 
35 
36 
37 XmlRpcClient::XmlRpcClient(const char* host, int port, const char* uri/*=0*/)
38 {
39  XmlRpcUtil::log(1, "XmlRpcClient new client: host %s, port %d.", host, port);
40 
41  _host = host;
42  _port = port;
43  if (uri && *uri)
44  _uri = uri;
45  else
46  _uri = "/RPC2";
47  _connectionState = NO_CONNECTION;
48  _executing = false;
49  _eof = false;
50  _ssl = false; _ssl_ssl = (SSL *) NULL;
51 
52  // Default to keeping the connection open until an explicit close is done
53  setKeepOpen();
54 }
55 XmlRpcClient::XmlRpcClient(const char* host, int port, const char* uri, bool ssl)
56 {
57  XmlRpcUtil::log(1, "XmlRpcClient new client: host %s, port %d.", host, port);
58 
59  _host = host;
60  _port = port;
61  if (uri && *uri)
62  _uri = uri;
63  else
64  _uri = "/RPC2";
65  _connectionState = NO_CONNECTION;
66  _executing = false;
67  _eof = false;
68  _ssl = ssl;
69  if (!_ssl) { _ssl_ssl = (SSL *) NULL; }
70 
71  // Default to keeping the connection open until an explicit close is done
72  setKeepOpen();
73 }
74 
75 
76 XmlRpcClient::XmlRpcClient(const char* host, int port,
77  const char* login, const char* password, const char* uri/*=0*/)
78 {
79  XmlRpcUtil::log(1, "XmlRpcClient new client: host %s, port %d, login %s.", host, port, login);
80 
81  _host = host;
82  _port = port;
83  if (uri)
84  _uri = uri;
85  else
86  _uri = "/RPC2";
87 
88  _login = login;
89  _password = password;
90 
91  _connectionState = NO_CONNECTION;
92  _executing = false;
93  _eof = false;
94 
95  // Default to keeping the connection open until an explicit close is done
96  setKeepOpen();
97 }
98 
99 XmlRpcClient::XmlRpcClient(const char* host, int port,
100  const char* login, const char* password,
101  const char* uri/*=0*/, bool ssl)
102 {
103  XmlRpcUtil::log(1, "XmlRpcClient new client: host %s, port %d, login %s.", host, port, login);
104 
105  _host = host;
106  _port = port;
107  if (uri)
108  _uri = uri;
109  else
110  _uri = "/RPC2";
111 
112  _login = login;
113  _password = password;
114 
115  _connectionState = NO_CONNECTION;
116  _executing = false;
117  _eof = false;
118  _ssl = ssl;
119  if (!_ssl) { _ssl_ssl = (SSL *) NULL; }
120 
121  // Default to keeping the connection open until an explicit close is done
122  setKeepOpen();
123 }
124 
125 
127 {
128  XmlRpcUtil::log(1, "XmlRpcClient dtor client: host %s, port %d.", _host.c_str(), _port);
129  if (_connectionState != NO_CONNECTION) close();
130 }
131 
132 
133 // Close the owned fd
134 void
136 {
137  XmlRpcUtil::log(4, "XmlRpcClient::close: fd %d.", getfd());
138  _connectionState = NO_CONNECTION;
139  _disp.exit();
140  _disp.removeSource(this);
141  if (_ssl) {
142  // Pre-socket shutdown
143  XmlRpcUtil::log(4, "XmlRpcClient::close: before SSL_shutdown");
144  //SSL_shutdown(_ssl_ssl);
145  XmlRpcUtil::log(4, "XmlRpcClient::close: after SSL_shutdown");
146  }
148  if (_ssl) {
149  // Post-socket shutdown
150  XmlRpcUtil::log(4, "XmlRpcClient::close: before SSL_free(_ssl_ssl)");
151  //SSL_free(_ssl_ssl);
152  XmlRpcUtil::log(4, "XmlRpcClient::close: before SSL_CTX_free(_ssl_ctx)");
153  //SSL_CTX_free(_ssl_ctx);
154  XmlRpcUtil::log(4, "XmlRpcClient::close: SSL shutdown successful!");
155  }
156 }
157 
158 
159 // Clear the referenced flag even if exceptions or errors occur.
161  ClearFlagOnExit(bool& flag) : _flag(flag) {}
162  ~ClearFlagOnExit() { _flag = false; }
163  bool& _flag;
164 };
165 
166 // Execute the named procedure on the remote server.
167 // Params should be an array of the arguments for the method.
168 // Returns true if the request was sent and a result received (although the result
169 // might be a fault).
170 bool
171 XmlRpcClient::execute(const char* method, XmlRpcValue const& params, XmlRpcValue& result)
172 {
173  XmlRpcUtil::log(1, "XmlRpcClient::execute: method %s (_connectionState %d).", method, _connectionState);
174 
175  // This is not a thread-safe operation, if you want to do multithreading, use separate
176  // clients for each thread. If you want to protect yourself from multiple threads
177  // accessing the same client, replace this code with a real mutex.
178  if (_executing)
179  return false;
180 
181  _executing = true;
182  ClearFlagOnExit cf(_executing);
183 
184  _sendAttempts = 0;
185  _isFault = false;
186 
187  if ( ! setupConnection())
188  return false;
189 
190  if ( ! generateRequest(method, params))
191  return false;
192 
193  result.clear();
194  double msTime = -1.0; // Process until exit is called
195  _disp.work(msTime);
196 
197  if (_connectionState != IDLE || ! parseResponse(result))
198  return false;
199 
200  XmlRpcUtil::log(1, "XmlRpcClient::execute: method %s completed.", method);
201  _response = "";
202  return true;
203 }
204 
205 // XmlRpcSource interface implementation
206 // Handle server responses. Called by the event dispatcher during execute.
207 unsigned
208 XmlRpcClient::handleEvent(unsigned eventType)
209 {
210  if (eventType == XmlRpcDispatch::Exception)
211  {
212  //if (XmlRpcSocket::nonFatalError())
213  // return (_connectionState == WRITE_REQUEST)
214  // ? XmlRpcDispatch::WritableEvent : XmlRpcDispatch::ReadableEvent;
215 
216  if (_connectionState == WRITE_REQUEST && _bytesWritten == 0)
217  XmlRpcUtil::error("Error in XmlRpcClient::handleEvent: could not connect to server (%s).",
218  XmlRpcSocket::getErrorMsg().c_str());
219  else
220  XmlRpcUtil::error("Error in XmlRpcClient::handleEvent (state %d): %s.",
221  _connectionState, XmlRpcSocket::getErrorMsg().c_str());
222  return 0;
223  }
224 
225  if (_connectionState == WRITE_REQUEST)
226  if ( ! writeRequest()) return 0;
227 
228  if (_connectionState == READ_HEADER)
229  if ( ! readHeader()) return 0;
230 
231  if (_connectionState == READ_RESPONSE)
232  if ( ! readResponse()) return 0;
233 
234  // This should probably always ask for Exception events too
235  return (_connectionState == WRITE_REQUEST)
237 }
238 
239 
240 // Create the socket connection to the server if necessary
241 bool
243 {
244  // If an error occurred last time through, or if the server closed the connection, close our end
245  if ((_connectionState != NO_CONNECTION && _connectionState != IDLE) || _eof)
246  close();
247 
248  _eof = false;
249  if (_connectionState == NO_CONNECTION)
250  if (! doConnect())
251  return false;
252 
253  // Prepare to write the request
254  _connectionState = WRITE_REQUEST;
255  _bytesWritten = 0;
256 
257  // Notify the dispatcher to listen on this source (calls handleEvent when the socket is writable)
258  _disp.removeSource(this); // Make sure nothing is left over
260 
261  return true;
262 }
263 
264 
265 // Connect to the xmlrpc server
266 bool
268 {
269  int fd = XmlRpcSocket::getSocket();
270  if (fd < 0)
271  {
272  XmlRpcUtil::error("Error in XmlRpcClient::doConnect: Could not create socket (%s).", XmlRpcSocket::getErrorMsg().c_str());
273  return false;
274  }
275 
276  XmlRpcUtil::log(3, "XmlRpcClient::doConnect: fd %d.", fd);
277  this->setfd(fd);
278 
279  // Don't block on connect/reads/writes
280  if ( ! XmlRpcSocket::setNonBlocking(fd))
281  {
282  this->close();
283  XmlRpcUtil::error("Error in XmlRpcClient::doConnect: Could not set socket to non-blocking IO mode (%s).", XmlRpcSocket::getErrorMsg().c_str());
284  return false;
285  }
286 
287  if ( ! XmlRpcSocket::connect(fd, _host, _port))
288  {
289  this->close();
290  XmlRpcUtil::error("Error in XmlRpcClient::doConnect: Could not connect to server (%s).", XmlRpcSocket::getErrorMsg().c_str());
291  return false;
292  }
293 
294 #ifdef USE_SSL
295  // Perform SSL if needed
296  if (_ssl) {
298  _ssl_meth = SSLv23_client_method();
300  _ssl_ctx = SSL_CTX_new (_ssl_meth);
301  _ssl_ssl = SSL_new (_ssl_ctx);
302  SSL_set_fd (_ssl_ssl, fd);
303  int err = SSL_connect (_ssl_ssl);
304  }
305 #endif
306 
307 
308  return true;
309 }
310 
311 // Encode the request to call the specified method with the specified parameters into xml
312 bool
313 XmlRpcClient::generateRequest(const char* methodName, XmlRpcValue const& params)
314 {
315  std::string body = REQUEST_BEGIN;
316  body += methodName;
317  body += REQUEST_END_METHODNAME;
318 
319  // If params is an array, each element is a separate parameter
320  if (params.isValid()) {
321  body += PARAMS_TAG;
322  if (params.getType() == XmlRpcValue::TypeArray)
323  {
324  for (int i=0; i<params.size(); ++i) {
325  body += PARAM_TAG;
326  body += params[i].toXml();
327  body += PARAM_ETAG;
328  }
329  }
330  else
331  {
332  body += PARAM_TAG;
333  body += params.toXml();
334  body += PARAM_ETAG;
335  }
336 
337  body += PARAMS_ETAG;
338  }
339  body += REQUEST_END;
340 
341  std::string header = generateHeader(body);
342  XmlRpcUtil::log(4, "XmlRpcClient::generateRequest: header is %d bytes, content-length is %d.",
343  header.length(), body.length());
344 
345  _request = header + body;
346  return true;
347 }
348 
349 // Prepend http headers
350 std::string
351 XmlRpcClient::generateHeader(std::string const& body)
352 {
353  std::string header =
354  "POST " + _uri + " HTTP/1.1\r\n"
355  "User-Agent: ";
356  header += XMLRPC_VERSION;
357  header += "\r\nHost: ";
358  header += _host;
359 
360  char buff[40];
361  sprintf(buff,":%d\r\n", _port);
362 
363  header += buff;
364 
365  if (_login.length() != 0)
366  {
367  // convert to base64
368  std::vector<char> base64data;
369  int iostatus = 0;
370  base64<char> encoder;
371  std::back_insert_iterator<std::vector<char> > ins =
372  std::back_inserter(base64data);
373 
374  std::string authBuf = _login + ":" + _password;
375 
376  encoder.put(authBuf.begin(), authBuf.end(), ins, iostatus,
377  base64<>::crlf());
378 
379  header += "Authorization: Basic ";
380  std::string authEnc(base64data.begin(), base64data.end());
381  // handle pesky linefeed characters
382  string::size_type lf;
383  while ( (lf = authEnc.find("\r")) != string::npos ) {
384  authEnc.erase(lf, 1);
385  }
386  while ( (lf = authEnc.find("\n")) != string::npos ) {
387  authEnc.erase(lf, 1);
388  }
389  header += authEnc;
390  header += "\r\n";
391  }
392 
393  header += "Content-Type: text/xml\r\nContent-length: ";
394 
395  sprintf(buff,"%zu\r\n\r\n", body.size());
396 
397  return header + buff;
398 }
399 
400 bool
402 {
403  if (_bytesWritten == 0)
404  XmlRpcUtil::log(5, "XmlRpcClient::writeRequest (attempt %d):\n%s\n", _sendAttempts+1, _request.c_str());
405 
406  // Try to write the request
407  if ( ! XmlRpcSocket::nbWrite(this->getfd(), _request, &_bytesWritten, _ssl_ssl)) {
408  XmlRpcUtil::error("Error in XmlRpcClient::writeRequest: write error (%s).",XmlRpcSocket::getErrorMsg().c_str());
409  return false;
410  }
411 
412  XmlRpcUtil::log(3, "XmlRpcClient::writeRequest: wrote %d of %d bytes.", _bytesWritten, _request.length());
413 
414  // Wait for the result
415  if (_bytesWritten == int(_request.length())) {
416  _header = "";
417  _response = "";
418  _connectionState = READ_HEADER;
419  }
420  return true;
421 }
422 
423 
424 // Read the header from the response
425 bool
427 {
428  // Read available data
429  if ( ! XmlRpcSocket::nbRead(this->getfd(), _header, &_eof, _ssl_ssl) ||
430  (_eof && _header.length() == 0)) {
431 
432  // If we haven't read any data yet and this is a keep-alive connection, the server may
433  // have timed out, so we try one more time.
434  if (getKeepOpen() && _header.length() == 0 && _sendAttempts++ == 0) {
435  XmlRpcUtil::log(4, "XmlRpcClient::readHeader: re-trying connection");
437  _connectionState = NO_CONNECTION;
438  _eof = false;
439  return setupConnection();
440  }
441 
442  XmlRpcUtil::error("Error in XmlRpcClient::readHeader: error while reading header (%s) on fd %d.",
443  XmlRpcSocket::getErrorMsg().c_str(), getfd());
444  return false;
445  }
446 
447  XmlRpcUtil::log(4, "XmlRpcClient::readHeader: client has read %d bytes", _header.length());
448 
449  char *hp = (char*)_header.c_str(); // Start of header
450  char *ep = hp + _header.length(); // End of string
451  char *bp = 0; // Start of body
452  char *lp = 0; // Start of content-length value
453 
454  for (char *cp = hp; (bp == 0) && (cp < ep); ++cp) {
455  if ((ep - cp > 16) && (strncasecmp(cp, "Content-length: ", 16) == 0))
456  lp = cp + 16;
457  else if ((ep - cp > 4) && (strncmp(cp, "\r\n\r\n", 4) == 0))
458  bp = cp + 4;
459  else if ((ep - cp > 2) && (strncmp(cp, "\n\n", 2) == 0))
460  bp = cp + 2;
461  }
462 
463  // If we haven't gotten the entire header yet, return (keep reading)
464  if (bp == 0) {
465  if (_eof) // EOF in the middle of a response is an error
466  {
467  XmlRpcUtil::error("Error in XmlRpcClient::readHeader: EOF while reading header");
468  return false; // Close the connection
469  }
470 
471  return true; // Keep reading
472  }
473 
474  // Decode content length
475  if (lp == 0) {
476  XmlRpcUtil::error("Error XmlRpcClient::readHeader: No Content-length specified");
477  return false; // We could try to figure it out by parsing as we read, but for now...
478  }
479 
480  _contentLength = atoi(lp);
481  if (_contentLength <= 0) {
482  XmlRpcUtil::error("Error in XmlRpcClient::readHeader: Invalid Content-length specified (%d).", _contentLength);
483  return false;
484  }
485 
486  XmlRpcUtil::log(4, "client read content length: %d", _contentLength);
487 
488  // Otherwise copy non-header data to response buffer and set state to read response.
489  _response = bp;
490  _header = ""; // should parse out any interesting bits from the header (connection, etc)...
491  _connectionState = READ_RESPONSE;
492  return true; // Continue monitoring this source
493 }
494 
495 
496 bool
498 {
499  // If we dont have the entire response yet, read available data
500  if (int(_response.length()) < _contentLength) {
501  if ( ! XmlRpcSocket::nbRead(this->getfd(), _response, &_eof, _ssl_ssl)) {
502  XmlRpcUtil::error("Error in XmlRpcClient::readResponse: read error (%s).",XmlRpcSocket::getErrorMsg().c_str());
503  return false;
504  }
505 
506  // If we haven't gotten the entire _response yet, return (keep reading)
507  if (int(_response.length()) < _contentLength) {
508  if (_eof) {
509  XmlRpcUtil::error("Error in XmlRpcClient::readResponse: EOF while reading response");
510  return false;
511  }
512  return true;
513  }
514  }
515 
516  // Otherwise, parse and return the result
517  XmlRpcUtil::log(3, "XmlRpcClient::readResponse (read %d bytes)", _response.length());
518  XmlRpcUtil::log(5, "response:\n%s", _response.c_str());
519 
520  _connectionState = IDLE;
521 
522  return false; // Stop monitoring this source (causes return from work)
523 }
524 
525 
526 // Convert the response xml into a result value
527 bool
529 {
530  // Parse response xml into result
531  int offset = 0;
532  if ( ! XmlRpcUtil::findTag(METHODRESPONSE_TAG,_response,&offset)) {
533  XmlRpcUtil::error("Error in XmlRpcClient::parseResponse: Invalid response - no methodResponse. Response:\n%s", _response.c_str());
534  return false;
535  }
536 
537  // Expect either <params><param>... or <fault>...
538  if ((XmlRpcUtil::nextTagIs(PARAMS_TAG,_response,&offset) &&
539  XmlRpcUtil::nextTagIs(PARAM_TAG,_response,&offset)) ||
540  (XmlRpcUtil::nextTagIs(FAULT_TAG,_response,&offset) && (_isFault = true)))
541  {
542  if ( ! result.fromXml(_response, &offset)) {
543  XmlRpcUtil::error("Error in XmlRpcClient::parseResponse: Invalid response value. Response:\n%s", _response.c_str());
544  _response = "";
545  return false;
546  }
547  } else {
548  XmlRpcUtil::error("Error in XmlRpcClient::parseResponse: Invalid response - no param or fault tag. Response:\n%s", _response.c_str());
549  _response = "";
550  return false;
551  }
552 
553  _response = "";
554  return result.isValid();
555 }
556