aboutsummaryrefslogtreecommitdiff
path: root/xmlrpc++/src/XmlRpcServerConnection.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xmlrpc++/src/XmlRpcServerConnection.cpp')
-rw-r--r--xmlrpc++/src/XmlRpcServerConnection.cpp371
1 files changed, 371 insertions, 0 deletions
diff --git a/xmlrpc++/src/XmlRpcServerConnection.cpp b/xmlrpc++/src/XmlRpcServerConnection.cpp
new file mode 100644
index 0000000..b9d6def
--- /dev/null
+++ b/xmlrpc++/src/XmlRpcServerConnection.cpp
@@ -0,0 +1,371 @@
+
+#include "XmlRpcServerConnection.h"
+
+#include "XmlRpcSocket.h"
+#include "XmlRpc.h"
+#ifndef MAKEDEPEND
+# include <stdio.h>
+# include <stdlib.h>
+#endif
+
+using namespace XmlRpc;
+
+// Static data
+const char XmlRpcServerConnection::METHODNAME_TAG[] = "<methodName>";
+const char XmlRpcServerConnection::PARAMS_TAG[] = "<params>";
+const char XmlRpcServerConnection::PARAMS_ETAG[] = "</params>";
+const char XmlRpcServerConnection::PARAM_TAG[] = "<param>";
+const char XmlRpcServerConnection::PARAM_ETAG[] = "</param>";
+
+const std::string XmlRpcServerConnection::SYSTEM_MULTICALL = "system.multicall";
+const std::string XmlRpcServerConnection::METHODNAME = "methodName";
+const std::string XmlRpcServerConnection::PARAMS = "params";
+
+const std::string XmlRpcServerConnection::FAULTCODE = "faultCode";
+const std::string XmlRpcServerConnection::FAULTSTRING = "faultString";
+
+
+
+// The server delegates handling client requests to a serverConnection object.
+XmlRpcServerConnection::XmlRpcServerConnection(int fd, XmlRpcServer* server, bool deleteOnClose /*= false*/) :
+ XmlRpcSource(fd, deleteOnClose)
+{
+ XmlRpcUtil::log(2,"XmlRpcServerConnection: new socket %d.", fd);
+ _server = server;
+ _connectionState = READ_HEADER;
+ _keepAlive = true;
+}
+
+
+XmlRpcServerConnection::~XmlRpcServerConnection()
+{
+ XmlRpcUtil::log(4,"XmlRpcServerConnection dtor.");
+ _server->removeConnection(this);
+}
+
+
+// Handle input on the server socket by accepting the connection
+// and reading the rpc request. Return true to continue to monitor
+// the socket for events, false to remove it from the dispatcher.
+unsigned
+XmlRpcServerConnection::handleEvent(unsigned /*eventType*/)
+{
+ if (_connectionState == READ_HEADER)
+ if ( ! readHeader()) return 0;
+
+ if (_connectionState == READ_REQUEST)
+ if ( ! readRequest()) return 0;
+
+ if (_connectionState == WRITE_RESPONSE)
+ if ( ! writeResponse()) return 0;
+
+ return (_connectionState == WRITE_RESPONSE)
+ ? XmlRpcDispatch::WritableEvent : XmlRpcDispatch::ReadableEvent;
+}
+
+
+bool
+XmlRpcServerConnection::readHeader()
+{
+ // Read available data
+ bool eof;
+ if ( ! XmlRpcSocket::nbRead(this->getfd(), _header, &eof)) {
+ // Its only an error if we already have read some data
+ if (_header.length() > 0)
+ XmlRpcUtil::error("XmlRpcServerConnection::readHeader: error while reading header (%s).",XmlRpcSocket::getErrorMsg().c_str());
+ return false;
+ }
+
+ XmlRpcUtil::log(4, "XmlRpcServerConnection::readHeader: read %d 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
+ char *kp = 0; // Start of connection 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 > 12) && (strncasecmp(cp, "Connection: ", 12) == 0))
+ kp = cp + 12;
+ 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) {
+ // EOF in the middle of a request is an error, otherwise its ok
+ if (eof) {
+ XmlRpcUtil::log(4, "XmlRpcServerConnection::readHeader: EOF");
+ if (_header.length() > 0)
+ XmlRpcUtil::error("XmlRpcServerConnection::readHeader: EOF while reading header");
+ return false; // Either way we close the connection
+ }
+
+ return true; // Keep reading
+ }
+
+ // Decode content length
+ if (lp == 0) {
+ XmlRpcUtil::error("XmlRpcServerConnection::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("XmlRpcServerConnection::readHeader: Invalid Content-length specified (%d).", _contentLength);
+ return false;
+ }
+
+ XmlRpcUtil::log(3, "XmlRpcServerConnection::readHeader: specified content length is %d.", _contentLength);
+
+ // Otherwise copy non-header data to request buffer and set state to read request.
+ _request = bp;
+
+ // Parse out any interesting bits from the header (HTTP version, connection)
+ _keepAlive = true;
+ if (_header.find("HTTP/1.0") != std::string::npos) {
+ if (kp == 0 || strncasecmp(kp, "keep-alive", 10) != 0)
+ _keepAlive = false; // Default for HTTP 1.0 is to close the connection
+ } else {
+ if (kp != 0 && strncasecmp(kp, "close", 5) == 0)
+ _keepAlive = false;
+ }
+ XmlRpcUtil::log(3, "KeepAlive: %d", _keepAlive);
+
+
+ _header = "";
+ _connectionState = READ_REQUEST;
+ return true; // Continue monitoring this source
+}
+
+bool
+XmlRpcServerConnection::readRequest()
+{
+ // If we dont have the entire request yet, read available data
+ if (int(_request.length()) < _contentLength) {
+ bool eof;
+ if ( ! XmlRpcSocket::nbRead(this->getfd(), _request, &eof)) {
+ XmlRpcUtil::error("XmlRpcServerConnection::readRequest: read error (%s).",XmlRpcSocket::getErrorMsg().c_str());
+ return false;
+ }
+
+ // If we haven't gotten the entire request yet, return (keep reading)
+ if (int(_request.length()) < _contentLength) {
+ if (eof) {
+ XmlRpcUtil::error("XmlRpcServerConnection::readRequest: EOF while reading request");
+ return false; // Either way we close the connection
+ }
+ return true;
+ }
+ }
+
+ // Otherwise, parse and dispatch the request
+ XmlRpcUtil::log(3, "XmlRpcServerConnection::readRequest read %d bytes.", _request.length());
+ //XmlRpcUtil::log(5, "XmlRpcServerConnection::readRequest:\n%s\n", _request.c_str());
+
+ _connectionState = WRITE_RESPONSE;
+
+ return true; // Continue monitoring this source
+}
+
+
+bool
+XmlRpcServerConnection::writeResponse()
+{
+ if (_response.length() == 0) {
+ executeRequest();
+ _bytesWritten = 0;
+ if (_response.length() == 0) {
+ XmlRpcUtil::error("XmlRpcServerConnection::writeResponse: empty response.");
+ return false;
+ }
+ }
+
+ // Try to write the response
+ if ( ! XmlRpcSocket::nbWrite(this->getfd(), _response, &_bytesWritten)) {
+ XmlRpcUtil::error("XmlRpcServerConnection::writeResponse: write error (%s).",XmlRpcSocket::getErrorMsg().c_str());
+ return false;
+ }
+ XmlRpcUtil::log(3, "XmlRpcServerConnection::writeResponse: wrote %d of %d bytes.", _bytesWritten, _response.length());
+
+ // Prepare to read the next request
+ if (_bytesWritten == int(_response.length())) {
+ _header = "";
+ _request = "";
+ _response = "";
+ _connectionState = READ_HEADER;
+ }
+
+ return _keepAlive; // Continue monitoring this source if true
+}
+
+// Run the method, generate _response string
+void
+XmlRpcServerConnection::executeRequest()
+{
+ XmlRpcValue params, resultValue;
+ std::string methodName = parseRequest(params);
+ XmlRpcUtil::log(2, "XmlRpcServerConnection::executeRequest: server calling method '%s'",
+ methodName.c_str());
+
+ try {
+
+ if ( ! executeMethod(methodName, params, resultValue) &&
+ ! executeMulticall(methodName, params, resultValue))
+ generateFaultResponse(methodName + ": unknown method name");
+ else
+ generateResponse(resultValue.toXml());
+
+ } catch (const XmlRpcException& fault) {
+ XmlRpcUtil::log(2, "XmlRpcServerConnection::executeRequest: fault %s.",
+ fault.getMessage().c_str());
+ generateFaultResponse(fault.getMessage(), fault.getCode());
+ }
+}
+
+// Parse the method name and the argument values from the request.
+std::string
+XmlRpcServerConnection::parseRequest(XmlRpcValue& params)
+{
+ int offset = 0; // Number of chars parsed from the request
+
+ std::string methodName = XmlRpcUtil::parseTag(METHODNAME_TAG, _request, &offset);
+
+ if (methodName.size() > 0 && XmlRpcUtil::findTag(PARAMS_TAG, _request, &offset))
+ {
+ int nArgs = 0;
+ while (XmlRpcUtil::nextTagIs(PARAM_TAG, _request, &offset)) {
+ params[nArgs++] = XmlRpcValue(_request, &offset);
+ (void) XmlRpcUtil::nextTagIs(PARAM_ETAG, _request, &offset);
+ }
+
+ (void) XmlRpcUtil::nextTagIs(PARAMS_ETAG, _request, &offset);
+ }
+
+ return methodName;
+}
+
+// Execute a named method with the specified params.
+bool
+XmlRpcServerConnection::executeMethod(const std::string& methodName,
+ XmlRpcValue& params, XmlRpcValue& result)
+{
+ XmlRpcServerMethod* method = _server->findMethod(methodName);
+
+ if ( ! method) return false;
+
+ method->execute(params, result);
+
+ // Ensure a valid result value
+ if ( ! result.valid())
+ result = std::string();
+
+ return true;
+}
+
+// Execute multiple calls and return the results in an array.
+bool
+XmlRpcServerConnection::executeMulticall(const std::string& methodName,
+ XmlRpcValue& params, XmlRpcValue& result)
+{
+ if (methodName != SYSTEM_MULTICALL) return false;
+
+ // There ought to be 1 parameter, an array of structs
+ if (params.size() != 1 || params[0].getType() != XmlRpcValue::TypeArray)
+ throw XmlRpcException(SYSTEM_MULTICALL + ": Invalid argument (expected an array)");
+
+ int nc = params[0].size();
+ result.setSize(nc);
+
+ for (int i=0; i<nc; ++i) {
+
+ if ( ! params[0][i].hasMember(METHODNAME) ||
+ ! params[0][i].hasMember(PARAMS)) {
+ result[i][FAULTCODE] = -1;
+ result[i][FAULTSTRING] = SYSTEM_MULTICALL +
+ ": Invalid argument (expected a struct with members methodName and params)";
+ continue;
+ }
+
+ const std::string& methodName = params[0][i][METHODNAME];
+ XmlRpcValue& methodParams = params[0][i][PARAMS];
+
+ XmlRpcValue resultValue;
+ resultValue.setSize(1);
+ try {
+ if ( ! executeMethod(methodName, methodParams, resultValue[0]) &&
+ ! executeMulticall(methodName, params, resultValue[0]))
+ {
+ result[i][FAULTCODE] = -1;
+ result[i][FAULTSTRING] = methodName + ": unknown method name";
+ }
+ else
+ result[i] = resultValue;
+
+ } catch (const XmlRpcException& fault) {
+ result[i][FAULTCODE] = fault.getCode();
+ result[i][FAULTSTRING] = fault.getMessage();
+ }
+ }
+
+ return true;
+}
+
+
+// Create a response from results xml
+void
+XmlRpcServerConnection::generateResponse(std::string const& resultXml)
+{
+ const char RESPONSE_1[] =
+ "<?xml version=\"1.0\"?>\r\n"
+ "<methodResponse><params><param>\r\n\t";
+ const char RESPONSE_2[] =
+ "\r\n</param></params></methodResponse>\r\n";
+
+ std::string body = RESPONSE_1 + resultXml + RESPONSE_2;
+ std::string header = generateHeader(body);
+
+ _response = header + body;
+ XmlRpcUtil::log(5, "XmlRpcServerConnection::generateResponse:\n%s\n", _response.c_str());
+}
+
+// Prepend http headers
+std::string
+XmlRpcServerConnection::generateHeader(std::string const& body)
+{
+ std::string header =
+ "HTTP/1.1 200 OK\r\n"
+ "Server: ";
+ header += XMLRPC_VERSION;
+ header += "\r\n"
+ "Content-Type: text/xml\r\n"
+ "Content-length: ";
+
+ char buffLen[40];
+ sprintf(buffLen,"%d\r\n\r\n", body.size());
+
+ return header + buffLen;
+}
+
+
+void
+XmlRpcServerConnection::generateFaultResponse(std::string const& errorMsg, int errorCode)
+{
+ const char RESPONSE_1[] =
+ "<?xml version=\"1.0\"?>\r\n"
+ "<methodResponse><fault>\r\n\t";
+ const char RESPONSE_2[] =
+ "\r\n</fault></methodResponse>\r\n";
+
+ XmlRpcValue faultStruct;
+ faultStruct[FAULTCODE] = errorCode;
+ faultStruct[FAULTSTRING] = errorMsg;
+ std::string body = RESPONSE_1 + faultStruct.toXml() + RESPONSE_2;
+ std::string header = generateHeader(body);
+
+ _response = header + body;
+}
+