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