Files
cantata/libmaia/maiaXmlRpcServerConnection.cpp
2011-12-04 10:50:02 +00:00

213 lines
6.7 KiB
C++

/*
* libMaia - maiaXmlRpcServerConnection.cpp
* Copyright (c) 2007 Sebastian Wiedenroth <wiedi@frubar.net>
* 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<QVariant> 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<MaiaFault>()) {
response = ret.value<MaiaFault>().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<QByteArray> 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<QByteArray> 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();
}