/* * libMaia - maiaXmlRpcServerConnection.cpp * Copyright (c) 2007 Sebastian Wiedenroth * and Karl Glatz * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "maiaXmlRpcServerConnection.h" #include "maiaXmlRpcServer.h" MaiaXmlRpcServerConnection::MaiaXmlRpcServerConnection(QTcpSocket *connection, QObject* parent) : QObject(parent) { header = NULL; clientConnection = connection; connect(clientConnection, SIGNAL(readyRead()), this, SLOT(readFromSocket())); connect(clientConnection, SIGNAL(disconnected()), this, SLOT(deleteLater())); } MaiaXmlRpcServerConnection::~MaiaXmlRpcServerConnection() { clientConnection->deleteLater(); delete header; } void MaiaXmlRpcServerConnection::readFromSocket() { QString lastLine; while(clientConnection->canReadLine() && !header) { lastLine = clientConnection->readLine(); headerString += lastLine; if(lastLine == "\r\n") { /* http header end */ header = new QHttpRequestHeader(headerString); if(!header->isValid()) { /* return http error */ qDebug() << "Invalid Header"; return; } else if(header->method() != "POST") { /* return http error */ qDebug() << "No Post!"; return; } else if(!header->contentLength()) { /* return fault */ qDebug() << "No Content Length"; return; } } } if(header) { if(header->contentLength() <= clientConnection->bytesAvailable()) { /* all data complete */ parseCall(clientConnection->readAll()); } } } void MaiaXmlRpcServerConnection::sendResponse(QString content) { QHttpResponseHeader header(200, "Ok"); QByteArray block; header.setValue("Server", "MaiaXmlRpc/0.1"); header.setValue("Content-Type", "text/xml"); header.setValue("Connection","close"); block.append(header.toString().toUtf8()); block.append(content.toUtf8()); clientConnection->write(block); clientConnection->disconnectFromHost(); } void MaiaXmlRpcServerConnection::parseCall(QString call) { QDomDocument doc; QList args; QVariant ret; QString response; QObject *responseObject; const char *responseSlot; if(!doc.setContent(call)) { /* recieved invalid xml */ MaiaFault fault(-32700, "parse error: not well formed"); sendResponse(fault.toString()); return; } QDomElement methodNameElement = doc.documentElement().firstChildElement("methodName"); QDomElement params = doc.documentElement().firstChildElement("params"); if(methodNameElement.isNull()) { /* invalid call */ MaiaFault fault(-32600, "server error: invalid xml-rpc. not conforming to spec"); sendResponse(fault.toString()); return; } QString methodName = methodNameElement.text(); emit getMethod(methodName, &responseObject, &responseSlot); if(!responseObject) { /* unknown method */ MaiaFault fault(-32601, "server error: requested method not found"); sendResponse(fault.toString()); return; } QDomNode paramNode = params.firstChild(); while(!paramNode.isNull()) { args << MaiaObject::fromXml( paramNode.firstChild().toElement()); paramNode = paramNode.nextSibling(); } if(!invokeMethodWithVariants(responseObject, responseSlot, args, &ret)) { /* error invoking... */ MaiaFault fault(-32602, "server error: invalid method parameters"); sendResponse(fault.toString()); return; } if(ret.canConvert()) { response = ret.value().toString(); } else { response = MaiaObject::prepareResponse(ret); } sendResponse(response); } /* taken from http://delta.affinix.com/2006/08/14/invokemethodwithvariants/ thanks to Justin Karneges once again :) */ bool MaiaXmlRpcServerConnection::invokeMethodWithVariants(QObject *obj, const QByteArray &method, const QVariantList &args, QVariant *ret, Qt::ConnectionType type) { // QMetaObject::invokeMethod() has a 10 argument maximum if(args.count() > 10) return false; QList argTypes; for(int n = 0; n < args.count(); ++n) argTypes += args[n].typeName(); // get return type int metatype = 0; QByteArray retTypeName = getReturnType(obj->metaObject(), method, argTypes); if(!retTypeName.isEmpty() && retTypeName != "QVariant") { metatype = QMetaType::type(retTypeName.data()); if(metatype == 0) // lookup failed return false; } QGenericArgument arg[10]; for(int n = 0; n < args.count(); ++n) arg[n] = QGenericArgument(args[n].typeName(), args[n].constData()); QGenericReturnArgument retarg; QVariant retval; if(metatype != 0) { retval = QVariant(metatype, (const void *)0); retarg = QGenericReturnArgument(retval.typeName(), retval.data()); } else { /* QVariant */ retarg = QGenericReturnArgument("QVariant", &retval); } if(retTypeName.isEmpty()) { /* void */ if(!QMetaObject::invokeMethod(obj, method.data(), type, arg[0], arg[1], arg[2], arg[3], arg[4], arg[5], arg[6], arg[7], arg[8], arg[9])) return false; } else { if(!QMetaObject::invokeMethod(obj, method.data(), type, retarg, arg[0], arg[1], arg[2], arg[3], arg[4], arg[5], arg[6], arg[7], arg[8], arg[9])) return false; } if(retval.isValid() && ret) *ret = retval; return true; } QByteArray MaiaXmlRpcServerConnection::getReturnType(const QMetaObject *obj, const QByteArray &method, const QList argTypes) { for(int n = 0; n < obj->methodCount(); ++n) { QMetaMethod m = obj->method(n); QByteArray sig = m.signature(); int offset = sig.indexOf('('); if(offset == -1) continue; QByteArray name = sig.mid(0, offset); if(name != method) continue; if(m.parameterTypes() != argTypes) continue; return m.typeName(); } return QByteArray(); }