diff options
| author | Ian Geiser <ian.geiser@gmail.com> | 2009-11-08 22:02:28 +0000 |
|---|---|---|
| committer | Ian Geiser <ian.geiser@gmail.com> | 2009-11-08 22:02:28 +0000 |
| commit | 5b0870ddaac421af2639058648a218c7061cdd6f (patch) | |
| tree | 351dd9d9ecb92fb1db4ded641a986066ad888914 /source | |
| parent | 89e052995997c9a372d7deee6a9d31e100e10533 (diff) | |
| download | qxmpp-5b0870ddaac421af2639058648a218c7061cdd6f.tar.gz | |
Added start of XEP-009. This is only the inteface, being able to invoke remote methods is next.
Diffstat (limited to 'source')
| -rw-r--r-- | source/QXmppClient.cpp | 59 | ||||
| -rw-r--r-- | source/QXmppClient.h | 10 | ||||
| -rw-r--r-- | source/QXmppConstants.cpp | 1 | ||||
| -rw-r--r-- | source/QXmppConstants.h | 1 | ||||
| -rw-r--r-- | source/QXmppIbbTransferManager.h | 1 | ||||
| -rw-r--r-- | source/QXmppInformationRequestResult.cpp | 7 | ||||
| -rw-r--r-- | source/QXmppInvokable.cpp | 126 | ||||
| -rw-r--r-- | source/QXmppInvokable.h | 67 | ||||
| -rw-r--r-- | source/QXmppRpcIq.cpp | 173 | ||||
| -rw-r--r-- | source/QXmppRpcIq.h | 67 | ||||
| -rw-r--r-- | source/QXmppStream.cpp | 8 | ||||
| -rw-r--r-- | source/QXmppStream.h | 5 | ||||
| -rw-r--r-- | source/source.pro | 10 | ||||
| -rw-r--r-- | source/xmlrpc.cpp | 470 | ||||
| -rw-r--r-- | source/xmlrpc.h | 184 |
15 files changed, 1186 insertions, 3 deletions
diff --git a/source/QXmppClient.cpp b/source/QXmppClient.cpp index f94dd7da..c8b16041 100644 --- a/source/QXmppClient.cpp +++ b/source/QXmppClient.cpp @@ -28,6 +28,8 @@ #include "QXmppMessage.h" #include "QXmppReconnectionManager.h" #include "QXmppIbbTransferManager.h" +#include "QXmppInvokable.h" +#include "QXmppRpcIq.h" #include "QXmppUtils.h" /// Creates a QXmppClient object. @@ -375,7 +377,62 @@ QXmppVCardManager& QXmppClient::getVCardManager() return m_stream->getVCardManager(); } +void QXmppClient::addInvokableInterface( QXmppInvokable *interface ) +{ + m_interfaces[ interface->metaObject()->className() ] = interface; +} + + +void QXmppClient::invokeInterfaceMethod( const QXmppRpcInvokeIq &iq ) +{ + QXmppStanza::Error error; + QString interface = iq.getInterface(); + QXmppInvokable *iface = m_interfaces[ interface ]; + if( iface ) + { + if ( iface->isAuthorized( iq.getFrom() ) ) + { + + if ( iface->interfaces().contains( iq.getMethod() ) ) + { + QVariant result = iface->dispatch(iq.getMethod().toLatin1(), + iq.getPayload() ); + QXmppRpcResponseIq resultIq; + resultIq.setId(iq.getId()); + resultIq.setTo(iq.getFrom()); + resultIq.setFrom( m_config.getJid()); + resultIq.setPayload(result); + m_stream->sendPacket( resultIq ); + return; + } + else + { + error.setType(QXmppStanza::Error::Cancel); + error.setCondition(QXmppStanza::Error::ItemNotFound); + + } + } + else + { + error.setType(QXmppStanza::Error::Auth); + error.setCondition(QXmppStanza::Error::Forbidden); + } + } + else + { + error.setType(QXmppStanza::Error::Cancel); + error.setCondition(QXmppStanza::Error::ItemNotFound); + } + QXmppRpcErrorIq errorIq; + errorIq.setId(iq.getId()); + errorIq.setTo(iq.getFrom()); + errorIq.setFrom( m_config.getJid()); + errorIq.setQuery( iq ); + errorIq.setError( error ); + m_stream->sendPacket( errorIq ); +} + QXmppIbbTransferManager* QXmppClient::getIbbTransferManager() const { - return m_ibbTransferManager; + return m_ibbTransferManager; } diff --git a/source/QXmppClient.h b/source/QXmppClient.h index 56f8fd1a..06d2b7b6 100644 --- a/source/QXmppClient.h +++ b/source/QXmppClient.h @@ -41,6 +41,9 @@ #include <QObject> #include <QTcpSocket> +#include <QHash> +#include <QVariant> + #include "QXmppConfiguration.h" #include "QXmppPresence.h" @@ -52,6 +55,9 @@ class QXmppIq; class QXmppRoster; class QXmppReconnectionManager; class QXmppVCardManager; +class QXmppInvokable; +class QXmppRpcInvokeIq; +class QXmppRemoteMethod; class QXmppIbbTransferManager; class QXmppClient : public QObject @@ -150,6 +156,9 @@ signals: public: QAbstractSocket::SocketError getSocketError(); + void addInvokableInterface( QXmppInvokable *interface ); + void invokeInterfaceMethod( const QXmppRpcInvokeIq &iq ); + // QXmppStanza::Error getXmppStreamError(); public slots: @@ -168,6 +177,7 @@ private: ///< required for connecting to the XMPP server. QXmppPresence m_clientPrecence; ///< Stores the current presence of connected client QXmppReconnectionManager* m_reconnectionManager; ///< Pointer to the reconnection manager + QHash<QString,QXmppInvokable *> m_interfaces; QXmppIbbTransferManager* m_ibbTransferManager; ///< Pointer to the ibb transfer manager }; diff --git a/source/QXmppConstants.cpp b/source/QXmppConstants.cpp index 7fd2ead4..4c392f34 100644 --- a/source/QXmppConstants.cpp +++ b/source/QXmppConstants.cpp @@ -37,3 +37,4 @@ const char* ns_auth = "jabber:iq:auth"; const char* ns_authFeature = "http://jabber.org/features/iq-auth"; const char* ns_disco_info = "http://jabber.org/protocol/disco#info"; const char* ns_ibb = "http://jabber.org/protocol/ibb"; +const char* ns_rpc = "jabber:iq:rpc"; diff --git a/source/QXmppConstants.h b/source/QXmppConstants.h index 0e2553e9..4181fb14 100644 --- a/source/QXmppConstants.h +++ b/source/QXmppConstants.h @@ -38,5 +38,6 @@ extern const char* ns_auth; extern const char* ns_authFeature; extern const char* ns_disco_info; extern const char* ns_ibb; +extern const char* ns_rpc; #endif // QXMPPCONSTANTS_H diff --git a/source/QXmppIbbTransferManager.h b/source/QXmppIbbTransferManager.h index d812b3fe..014b20cf 100644 --- a/source/QXmppIbbTransferManager.h +++ b/source/QXmppIbbTransferManager.h @@ -9,6 +9,7 @@ class QXmppClient; class QXmppIbbTransferManager : public QObject
{
+ Q_OBJECT
public:
QXmppIbbTransferManager(QXmppClient* client);
diff --git a/source/QXmppInformationRequestResult.cpp b/source/QXmppInformationRequestResult.cpp index e882a6f2..4c268bc6 100644 --- a/source/QXmppInformationRequestResult.cpp +++ b/source/QXmppInformationRequestResult.cpp @@ -16,5 +16,12 @@ void QXmppInformationRequestResult::toXmlElementFromChild(QXmlStreamWriter *writ writer->writeStartElement("feature"); writer->writeAttribute("var", ns_ibb ); writer->writeEndElement(); + writer->writeStartElement("feature"); + writer->writeAttribute("var", ns_rpc); + writer->writeEndElement(); + writer->writeStartElement("identity"); + writer->writeAttribute("category", "automation" ); + writer->writeAttribute("type", "rpc" ); + writer->writeEndElement(); writer->writeEndElement(); } diff --git a/source/QXmppInvokable.cpp b/source/QXmppInvokable.cpp new file mode 100644 index 00000000..9127820c --- /dev/null +++ b/source/QXmppInvokable.cpp @@ -0,0 +1,126 @@ +/*************************************************************************** +* Copyright (C) 2006 by Ian Reinhart Geiser * +* geiseri@sourcextreme.com * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU Library General Public License as * +* published by the Free Software Foundation; either version 2 of the * +* License, or (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU Library General Public * +* License along with this program; if not, write to the * +* Free Software Foundation, Inc., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#include "QXmppInvokable.h" + +#include <QVariant> +#include <QMetaMethod> +#include <QStringList> + +#include <qdebug.h> + +QXmppInvokable::QXmppInvokable( QObject *parent ) + : QObject( parent ) +{ + +} + + +QXmppInvokable::~QXmppInvokable() +{ + +} + +QVariant QXmppInvokable::dispatch( const QByteArray & method, const QList< QVariant > & args ) +{ + buildMethodHash(); + + if( !m_methodHash.contains(method)) + return QVariant(); + + int idx = m_methodHash[method]; + if( paramTypes( args) != metaObject()->method(idx).parameterTypes ()) + return QVariant(); + + const char *typeName = metaObject()->method(idx).typeName(); + int resultType = QMetaType::type(typeName); + void *result = QMetaType::construct(resultType, 0); + + QGenericReturnArgument ret( typeName, result ); + QList<QGenericArgument> genericArgs; + QList<QVariant>::ConstIterator iter = args.begin(); + while( iter != args.end()) + { + const void *data = iter->data(); + const char *name = iter->typeName(); + genericArgs << QGenericArgument(name,data); + ++iter; + } + + if( QMetaObject::invokeMethod ( this, method.constData(), ret, + genericArgs.value(0, QGenericArgument() ), + genericArgs.value(1, QGenericArgument() ), + genericArgs.value(2, QGenericArgument() ), + genericArgs.value(3, QGenericArgument() ), + genericArgs.value(4, QGenericArgument() ), + genericArgs.value(5, QGenericArgument() ), + genericArgs.value(6, QGenericArgument() ), + genericArgs.value(7, QGenericArgument() ), + genericArgs.value(8, QGenericArgument() ), + genericArgs.value(9, QGenericArgument() )) ) + { + QVariant returnValue( resultType, result); + QMetaType::destroy(resultType, result); + return returnValue; + } + else + { + qDebug("No such method '%s'", method.constData() ); + return QVariant(); + } +} + +QList< QByteArray > QXmppInvokable::paramTypes( const QList< QVariant > & params ) +{ + QList<QByteArray> types; + foreach( QVariant variant, params) + types << variant.typeName(); + return types; +} + +void QXmppInvokable::buildMethodHash( ) +{ + QWriteLocker locker(&m_lock); + if( m_methodHash.size() > 0 ) + return; + + int methodCount = metaObject()->methodCount (); + for( int idx = 0; idx < methodCount; ++idx) + { + QByteArray signature = metaObject()->method(idx).signature(); + m_methodHash[signature.left(signature.indexOf('('))] = idx; +// qDebug() << metaObject()->method(idx).parameterTypes(); + } +} + +QStringList QXmppInvokable::interfaces( ) const +{ + QStringList results; + int methodCount = metaObject()->methodCount (); + for( int idx = 0; idx < methodCount; ++idx) + { + if( metaObject()->method(idx).methodType() == QMetaMethod::Slot ) + { + QByteArray signature = metaObject()->method(idx).signature(); + results << signature.left(signature.indexOf('(')); + } + } + return results; +} + diff --git a/source/QXmppInvokable.h b/source/QXmppInvokable.h new file mode 100644 index 00000000..c7f9c687 --- /dev/null +++ b/source/QXmppInvokable.h @@ -0,0 +1,67 @@ +/*************************************************************************** +* Copyright (C) 2006 by Ian Reinhart Geiser * +* geiseri@sourcextreme.com * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU Library General Public License as * +* published by the Free Software Foundation; either version 2 of the * +* License, or (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU Library General Public * +* License along with this program; if not, write to the * +* Free Software Foundation, Inc., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef QXMPPINVOKABLE_H +#define QXMPPINVOKABLE_H + +#include <QObject> +#include <QHash> +#include <QVariant> +#include <QWriteLocker> +#include <QStringList> + +/** +This is the base class for all objects that will be invokable via RPC. All public slots of objects derived from this class will be exposed to the RPC interface. As a note for all methods, they can only understand types that QVariant knows about. + + @author Ian Reinhart Geiser <geiseri@kde.org> +*/ +class QXmppInvokable : public QObject +{ + Q_OBJECT +public: + QXmppInvokable( QObject *parent = 0 ); + + ~QXmppInvokable(); + + /** + * Execute a method on an object. with a set of arguments. This method is reentrant, and the method + * that is invoked will be done in a thread safe manner. It should be noted that while this method + * is threadsafe and reentrant the side affects of the methods invoked may not be. + */ + QVariant dispatch( const QByteArray &method, const QList<QVariant> &args = QList<QVariant>() ); + + /** + * Utility method to convert a QList<QVariant> to a list of types for type + * checking. + */ + static QList<QByteArray> paramTypes( const QList<QVariant> ¶ms ); + + virtual bool isAuthorized( const QString &jid ) const = 0; + +public slots: + QStringList interfaces() const; + +private: + void buildMethodHash(); + QHash<QByteArray,int> m_methodHash; + QReadWriteLock m_lock; +}; + + +#endif diff --git a/source/QXmppRpcIq.cpp b/source/QXmppRpcIq.cpp new file mode 100644 index 00000000..8e7a6b99 --- /dev/null +++ b/source/QXmppRpcIq.cpp @@ -0,0 +1,173 @@ +#include "QXmppRpcIq.h" +#include "QXmppConstants.h" +#include "xmlrpc.h" + +#include <QDomElement> + +QXmppRpcErrorIq::QXmppRpcErrorIq() : QXmppIq( QXmppIq::Error ) +{ + +} + +void QXmppRpcErrorIq::toXmlElementFromChild(QXmlStreamWriter *writer) const +{ + m_query.toXmlElementFromChild(writer); +} + +void QXmppRpcErrorIq::setQuery(const QXmppRpcInvokeIq &query ) +{ + m_query = query; +} + +QXmppRpcInvokeIq QXmppRpcErrorIq::getQuery() const +{ + return m_query; +} + +void QXmppRpcErrorIq::parse( QDomElement &element ) +{ + QDomElement errorElement = element.firstChildElement("error"); + setId( element.attribute("id")); + setTo( element.attribute("to")); + setFrom( element.attribute("from")); + setTypeFromStr( element.attribute("type")); + + QXmppStanza::Error error; + error.setTypeFromStr( errorElement.attribute("type")); + + QDomElement conditionElement = errorElement.firstChildElement(); + while(!conditionElement.isNull()) + { + if(conditionElement.namespaceURI() == ns_stanza) + { + error.setConditionFromStr( conditionElement.tagName() ); + } + conditionElement = conditionElement.nextSiblingElement(); + } + + setError( error ); +} + +bool QXmppRpcErrorIq::isRpcErrorIq( QDomElement &element ) +{ + QString type = element.attribute("type"); + QDomElement errorElement = element.firstChildElement("error"); + QDomElement queryElement = element.firstChildElement("query"); + return (type == "error") && + !errorElement.isNull() && + queryElement.namespaceURI() == ns_rpc; +} + +QXmppRpcResponseIq::QXmppRpcResponseIq() : QXmppIq( QXmppIq::Result ) +{ +} + +void QXmppRpcResponseIq::toXmlElementFromChild(QXmlStreamWriter *writer) const +{ + XMLRPC::ResponseMessage message( m_payload ); + writer->writeStartElement(ns_rpc, "query"); + message.writeXml(writer); + writer->writeEndElement(); +} + +void QXmppRpcResponseIq::parse( QDomElement &element ) +{ + QDomElement queryElement = element.firstChildElement("query"); + setId( element.attribute("id")); + setTo( element.attribute("to")); + setFrom( element.attribute("from")); + setTypeFromStr( element.attribute("type")); + + QDomElement methodElement = queryElement.firstChildElement("methodResponse"); + + XMLRPC::ResponseMessage message( methodElement ); + m_payload = message.value(); + +} + +bool QXmppRpcResponseIq::isRpcResponseIq( QDomElement &element ) +{ + QString type = element.attribute("type"); + QDomElement dataElement = element.firstChildElement("query"); + return dataElement.namespaceURI() == ns_rpc && + type == "result"; +} + +QVariant QXmppRpcResponseIq::getPayload() const +{ + return m_payload; +} + +void QXmppRpcResponseIq::setPayload( const QVariant &payload ) +{ + m_payload = payload; +} + + +QXmppRpcInvokeIq::QXmppRpcInvokeIq() : QXmppIq( QXmppIq::Set ) +{ +} + +void QXmppRpcInvokeIq::toXmlElementFromChild(QXmlStreamWriter *writer) const +{ + QString methodName = m_interface + "." + m_method; + XMLRPC::RequestMessage message( methodName.toLatin1() ,m_payload ); + writer->writeStartElement(ns_rpc, "query"); + message.writeXml(writer); + writer->writeEndElement(); +} + +void QXmppRpcInvokeIq::parse( QDomElement &element ) +{ + QDomElement queryElement = element.firstChildElement("query"); + setId( element.attribute("id")); + setTo( element.attribute("to")); + setFrom( element.attribute("from")); + setTypeFromStr( element.attribute("type")); + + QDomElement methodElement = queryElement.firstChildElement("methodCall"); + + XMLRPC::RequestMessage message( methodElement ); + + m_interface = message.method().split('.').value(0); + m_method = message.method().split('.').value(1); + m_payload = message.args(); + +} + +bool QXmppRpcInvokeIq::isRpcInvokeIq( QDomElement &element ) +{ + QString type = element.attribute("type"); + QDomElement dataElement = element.firstChildElement("query"); + return dataElement.namespaceURI() == ns_rpc && + type == "set"; +} + + +QVariantList QXmppRpcInvokeIq::getPayload() const +{ + return m_payload; +} + +void QXmppRpcInvokeIq::setPayload( const QVariantList &payload ) +{ + m_payload = payload; +} + +QString QXmppRpcInvokeIq::getMethod() const +{ + return m_method; +} +void QXmppRpcInvokeIq::setMethod( const QString &method ) +{ + m_method = method; +} + +QString QXmppRpcInvokeIq::getInterface() const +{ + return m_interface; +} +void QXmppRpcInvokeIq::setInterface( const QString &interface ) +{ + m_interface = interface; +} diff --git a/source/QXmppRpcIq.h b/source/QXmppRpcIq.h new file mode 100644 index 00000000..4e469364 --- /dev/null +++ b/source/QXmppRpcIq.h @@ -0,0 +1,67 @@ +#ifndef QXMPPRPCIQ_H +#define QXMPPRPCIQ_H + +#include "QXmppIq.h" +#include <QVariant> + +class QXmlStreamWriter; +class QDomElement; + +class QXmppRpcResponseIq : public QXmppIq +{ +public: + QXmppRpcResponseIq(); + void toXmlElementFromChild(QXmlStreamWriter *writer) const; + void parse( QDomElement &element ); + static bool isRpcResponseIq( QDomElement &element ); + + QVariant getPayload() const; + void setPayload( const QVariant &payload ); + +private: + QVariant m_payload; + +}; + +class QXmppRpcInvokeIq : public QXmppIq +{ +public: + QXmppRpcInvokeIq(); + + void toXmlElementFromChild(QXmlStreamWriter *writer) const; + void parse( QDomElement &element ); + static bool isRpcInvokeIq( QDomElement &element ); + + QVariantList getPayload() const; + void setPayload( const QVariantList &payload ); + + QString getMethod() const; + void setMethod( const QString &method ); + + QString getInterface() const; + void setInterface( const QString &interface ); + + +private: + QVariantList m_payload; + QString m_method; + QString m_interface; + +}; + +class QXmppRpcErrorIq : public QXmppIq +{ +public: + QXmppRpcErrorIq(); + void toXmlElementFromChild(QXmlStreamWriter *writer) const; + void parse( QDomElement &element ); + static bool isRpcErrorIq( QDomElement &element ); + + void setQuery(const QXmppRpcInvokeIq &query ); + QXmppRpcInvokeIq getQuery() const; + +private: + QXmppRpcInvokeIq m_query; +}; + +#endif // QXMPPRPCIQ_H diff --git a/source/QXmppStream.cpp b/source/QXmppStream.cpp index 0971b83d..307ad543 100644 --- a/source/QXmppStream.cpp +++ b/source/QXmppStream.cpp @@ -39,8 +39,10 @@ #include "QXmppInformationRequestResult.h" #include "QXmppIbbIqs.h" #include "QXmppDataIq.h" +#include "QXmppRpcIq.h" #include "QXmppIbbTransferManager.h" #include "QXmppLogger.h" +#include "QXmppUtils.h" #include <QDomDocument> @@ -423,6 +425,12 @@ void QXmppStream::parser(const QByteArray& data) QXmppIbbTransferJob *mgr = m_client->getIbbTransferManager()->getIbbTransferJob(closeIqPacket.getId()); mgr->gotClose(closeIqPacket); } + else if( QXmppRpcInvokeIq::isRpcInvokeIq( nodeRecv ) ) + { + QXmppRpcInvokeIq rpcIqPacket; + rpcIqPacket.parse(nodeRecv); + m_client->invokeInterfaceMethod(rpcIqPacket); + } else if(id == m_sessionId) { // get back add configuration whether to send diff --git a/source/QXmppStream.h b/source/QXmppStream.h index 86a6bf62..8936abe3 100644 --- a/source/QXmppStream.h +++ b/source/QXmppStream.h @@ -43,6 +43,8 @@ class QXmppBind; class QXmppRosterIq; class QXmppVCard; class QXmppMessage; +class QXmppRpcResponseIq; +class QXmppRpcErrorIq; class QXmppStream : public QObject { @@ -83,6 +85,9 @@ signals: void rosterRequestIqReceived(const QXmppRosterIq&); void vCardIqReceived(const QXmppVCard&); + void rpcRequestFinished( const QString &id, const QXmppRpcResponseIq& result ); + void rpcRequestError( const QString &id, const QXmppRpcErrorIq& err ); + private slots: void socketHostFound(); void socketReadReady(); diff --git a/source/source.pro b/source/source.pro index d30fb475..a61972ed 100644 --- a/source/source.pro +++ b/source/source.pro @@ -36,7 +36,10 @@ HEADERS += QXmppUtils.h \ QXmppDataIq.h \ QXmppIbbIqs.h \ QXmppIbbTransferJob.h \ - QXmppIbbTransferManager.h + QXmppIbbTransferManager.h \ + xmlrpc.h \ + QXmppInvokable.h \ + QXmppRpcIq.h # Source files SOURCES += QXmppUtils.cpp \ @@ -62,4 +65,7 @@ SOURCES += QXmppUtils.cpp \ QXmppDataIq.cpp \ QXmppIbbIqs.cpp \ QXmppIbbTransferJob.cpp \ - QXmppIbbTransferManager.cpp + QXmppIbbTransferManager.cpp \ + xmlrpc.cpp \ + QXmppInvokable.cpp \ + QXmppRpcIq.cpp diff --git a/source/xmlrpc.cpp b/source/xmlrpc.cpp new file mode 100644 index 00000000..2ed2a6e2 --- /dev/null +++ b/source/xmlrpc.cpp @@ -0,0 +1,470 @@ +#include <xmlrpc.h> +#include <QMap> +#include <QVariant> +#include <QDateTime> +#include <QStringList> +#include <QTextStream> + +XMLRPC::RequestMessage::RequestMessage( const QByteArray &method, const QList<QVariant> &args ) +: MessageBase() +{ + m_method = method; + m_args = args; +} + +XMLRPC::RequestMessage::RequestMessage( const QByteArray &method, const QVariant &arg ) +: MessageBase() +{ + m_method = method; + m_args.append(arg); +} + +void XMLRPC::RequestMessage::writeXml( QXmlStreamWriter *writer ) const +{ + writer->writeStartElement("methodCall"); + writer->writeTextElement("methodName", m_method ); + if( !m_args.isEmpty() ) + { + writer->writeStartElement("params"); + foreach( QVariant arg, m_args) + { + writer->writeStartElement("param"); + marshall( writer, arg ); + writer->writeEndElement(); + } + writer->writeEndElement(); + } + writer->writeEndElement(); + +} + +QByteArray XMLRPC::RequestMessage::xml() const +{ + if( m_method.isEmpty() ) + return QByteArray(); + + QByteArray returnXML; + QXmlStreamWriter writer( &returnXML ); + writer.writeStartDocument(); + writeXml(&writer ); + writer.writeEndDocument(); + return returnXML; +} + +void XMLRPC::MessageBase::marshall( QXmlStreamWriter *writer, const QVariant &value ) const +{ + writer->writeStartElement("value"); + switch( value.type() ) + { + case QVariant::Int: + case QVariant::UInt: + case QVariant::LongLong: + case QVariant::ULongLong: + writer->writeTextElement("i4", value.toString()); + break; + case QVariant::Double: + writer->writeTextElement("double", value.toString()); + break; + case QVariant::Bool: + writer->writeTextElement("boolean", (value.toBool()?"true":"false") ); + break; + case QVariant::Date: + writer->writeTextElement("dateTime.iso8601", value.toDate().toString( Qt::ISODate ) ); + break; + case QVariant::DateTime: + writer->writeTextElement("dateTime.iso8601", value.toDateTime().toString( Qt::ISODate ) ); + break; + case QVariant::Time: + writer->writeTextElement("dateTime.iso8601", value.toTime().toString( Qt::ISODate ) ); + break; + case QVariant::StringList: + case QVariant::List: + { + writer->writeStartElement("array"); + writer->writeStartElement("data"); + foreach( QVariant item, value.toList() ) + marshall( writer, item ); + writer->writeEndElement(); + writer->writeEndElement(); + break; + } + case QVariant::Map: + { + writer->writeStartElement("struct"); + QMap<QString, QVariant> map = value.toMap(); + QMap<QString, QVariant>::ConstIterator index = map.begin(); + while( index != map.end() ) + { + writer->writeStartElement("member"); + writer->writeTextElement("name", index.key()); + marshall( writer, *index ); + writer->writeEndElement(); + ++index; + } + writer->writeEndElement(); + break; + } + case QVariant::ByteArray: + { + writer->writeTextElement("base64", value.toByteArray().toBase64() ); + break; + } + default: + { + if( value.canConvert(QVariant::String) ) + { + writer->writeTextElement( "string", value.toString() ); + } + else + { + + } + break; + } + } + writer->writeEndElement(); + } +XMLRPC::ResponseMessage::ResponseMessage( const QDomElement &element ) +{ + const QDomElement contents = element.firstChild().toElement(); + if( contents.tagName().toLower() == "params") + { + QDomNode param = contents.firstChild(); + while( !param.isNull() && isValid() ) + { + m_values.append( demarshall( param.firstChild().toElement() ) ); + param = param.nextSibling(); + } + } + else if( contents.tagName().toLower() == "fault") + { + const QDomElement errElement = contents.firstChild().toElement(); + const QVariant error = demarshall( errElement ); + + setError( QString("XMLRPC Fault %1: %2") + .arg(error.toMap()["faultCode"].toString() ) + .arg(error.toMap()["faultString"].toString() ) ); + } + else + { + setError("Bad XML response"); + } +} + +XMLRPC::ResponseMessage::ResponseMessage( const QByteArray &xml ) +: MessageBase() +{ + QDomDocument message; + QString xmlErrMsg; + int xmlErrLine; + int xmlErrCol; + if( message.setContent(xml, &xmlErrMsg, &xmlErrLine, &xmlErrCol) ) + { + const QDomElement contents = message.documentElement().firstChild().toElement(); + if( contents.tagName().toLower() == "params") + { + QDomNode param = contents.firstChild(); + while( !param.isNull() && isValid() ) + { + m_values.append( demarshall( param.firstChild().toElement() ) ); + param = param.nextSibling(); + } + } + else if( message.documentElement().firstChild().toElement().tagName().toLower() == "fault") + { + const QDomElement errElement = contents.firstChild().toElement(); + const QVariant error = demarshall( errElement ); + + setError( QString("XMLRPC Fault %1: %2") + .arg(error.toMap()["faultCode"].toString() ) + .arg(error.toMap()["faultString"].toString() ) ); + } + else + { + setError("Bad XML response"); + } + } + else + { + setError(QString( "XML Error: %1 at row %2 and col %3") + .arg(xmlErrMsg).arg(xmlErrLine).arg(xmlErrCol)); + } +} + +int XMLRPC::ResponseMessage::count() const +{ + return m_values.count(); +} + +QVariant XMLRPC::ResponseMessage::value( int index) const +{ + return m_values[index]; +} + +bool XMLRPC::MessageBase::isValid() const +{ + return m_valid; +} + +QString XMLRPC::MessageBase::error() const +{ + return m_message; +} + +QVariant XMLRPC::MessageBase::demarshall( const QDomElement &elem ) const +{ + if ( elem.tagName().toLower() != "value" ) + { + m_valid = false; + m_message = "bad param value"; + return QVariant(); + } + + if ( !elem.firstChild().isElement() ) + { + return QVariant( elem.text() ); + } + + const QDomElement typeData = elem.firstChild().toElement(); + const QString typeName = typeData.tagName().toLower(); + + if ( typeName == "string" ) + return QVariant( typeData.text() ); + else if (typeName == "int" || typeName == "i4" ) + { + bool ok = false; + QVariant val( typeData.text().toInt( &ok ) ); + if( ok ) + return val; + m_message = "I was looking for an integer but data was courupt"; + } + else if( typeName == "double" ) + { + bool ok = false; + QVariant val( typeData.text().toDouble( &ok ) ); + if( ok ) + return val; + m_message = "I was looking for an double but data was courupt"; + } + else if( typeName == "boolean" ) + return QVariant( ( typeData.text().toLower() == "true" || typeData.text() == "1")?true:false ); + else if( typeName == "datetime" || typeName == "dateTime.iso8601" ) + return QVariant( QDateTime::fromString( typeData.text(), Qt::ISODate ) ); + else if( typeName == "array" ) + { + QList<QVariant> arr; + QDomNode valueNode = typeData.firstChild().firstChild(); + while( !valueNode.isNull() && m_valid ) + { + arr.append( demarshall( valueNode.toElement() ) ); + valueNode = valueNode.nextSibling(); + } + return QVariant( arr ); + } + else if( typeName == "struct" ) + { + QMap<QString,QVariant> stct; + QDomNode valueNode = typeData.firstChild(); + while( !valueNode.isNull() && m_valid ) + { + const QDomElement memberNode = valueNode.toElement().elementsByTagName("name").item(0).toElement(); + const QDomElement dataNode = valueNode.toElement().elementsByTagName("value").item(0).toElement(); + stct[ memberNode.text() ] = demarshall( dataNode ); + valueNode = valueNode.nextSibling(); + } + return QVariant(stct); + } + else if( typeName == "base64" ) + { + QVariant returnVariant; + QByteArray dest; + QByteArray src = typeData.text().toLatin1(); + dest = QByteArray::fromBase64( src ); + QDataStream ds(&dest, QIODevice::ReadOnly); + ds.setVersion(QDataStream::Qt_4_0); + ds >> returnVariant; + if( returnVariant.isValid() ) + return returnVariant; + else + return QVariant( dest ); + } + setError(QString( "Cannot handle type %1").arg(typeName)); + return QVariant(); +} + +void XMLRPC::MessageBase::setError( const QString & message ) const +{ + m_valid = false; + m_message = message; +} + +XMLRPC::MessageBase::MessageBase( ) : m_valid(true) +{ +} + +XMLRPC::MessageBase::~MessageBase( ) +{ +} + + +QList< QVariant > XMLRPC::ResponseMessage::values() const +{ + return m_values; +} + +QList< QVariant > XMLRPC::RequestMessage::args() const +{ + return m_args; +} + +QByteArray XMLRPC::RequestMessage::method() const +{ + return m_method; +} + +XMLRPC::RequestMessage::RequestMessage( const QDomElement &element ) +{ + m_args.clear(); + m_method.clear(); + + const QDomElement methodName = element.firstChildElement("methodName"); + if( !methodName.isNull() ) + { + m_method = methodName.text().toLatin1(); + } + else + { + setError("Missing methodName property."); + return; + } + + const QDomElement methodParams = element.firstChildElement("params"); + if( !methodParams.isNull() ) + { + QDomNode param = methodParams.firstChild(); + while( !param.isNull() && isValid() ) + { + m_args.append( demarshall( param.firstChild().toElement() ) ); + param = param.nextSibling(); + } + } +} + +XMLRPC::RequestMessage::RequestMessage( const QByteArray & xml ) +{ + QDomDocument message; + QString xmlErrMsg; + int xmlErrLine; + int xmlErrCol; + m_args.clear(); + m_method.clear(); + + if( message.setContent(xml, &xmlErrMsg, &xmlErrLine, &xmlErrCol) ) + { + const QDomElement methodCall = message.firstChildElement("methodCall"); + if( !methodCall.isNull() ) + { + const QDomElement methodName = methodCall.firstChildElement("methodName"); + if( !methodName.isNull() ) + { + m_method = methodName.text().toLatin1(); + } + else + { + setError("Missing methodName property."); + return; + } + + const QDomElement methodParams = methodCall.firstChildElement("params"); + if( !methodParams.isNull() ) + { + QDomNode param = methodParams.firstChild(); + while( !param.isNull() && isValid() ) + { + m_args.append( demarshall( param.firstChild().toElement() ) ); + param = param.nextSibling(); + } + } + } + else + setError("Not a valid methodCall message."); + } + else + { + setError(QString( "XML Error: %1 at row %2 and col %3") + .arg(xmlErrMsg).arg(xmlErrLine).arg(xmlErrCol)); + } +} + +void XMLRPC::ResponseMessage::writeXml( QXmlStreamWriter *writer ) const +{ + writer->writeStartElement("methodResponse"); + + if( !m_values.isEmpty() ) + { + writer->writeStartElement("params"); + foreach( QVariant arg, m_values) + { + writer->writeStartElement("params"); + marshall( writer, arg ); + writer->writeEndElement(); + } + writer->writeEndElement(); + } + writer->writeEndElement(); +} + +QByteArray XMLRPC::ResponseMessage::xml( ) const +{ + QByteArray returnXML ; + QXmlStreamWriter writer(&returnXML); + writer.writeStartDocument(); + writeXml( &writer ); + writer.writeEndElement(); + writer.writeEndDocument(); + return returnXML; +} + +void XMLRPC::ResponseMessage::setValues( const QList< QVariant > vals ) +{ + m_values = vals; +} + +XMLRPC::ResponseMessage::ResponseMessage( const QList< QVariant > & theValue ) +: MessageBase(), m_values(theValue) +{ +} + +XMLRPC::FaultMessage::FaultMessage( int code, const QString & message ) : +ResponseMessage(QList<QVariant>() ) +{ + QList<QVariant> args; + QMap<QString,QVariant> fault; + fault["faultCode"] = code; + fault["faultString"] = message; + args << fault; + setValues(args); +} + +void XMLRPC::FaultMessage::writeXml( QXmlStreamWriter *writer ) const +{ + writer->writeStartElement("fault"); + marshall( writer, values().first() ); + writer->writeEndElement(); +} + +QByteArray XMLRPC::FaultMessage::xml( ) const +{ + QByteArray returnXML; + QXmlStreamWriter writer( &returnXML ); + writer.writeStartDocument(); + writeXml(&writer); + writer.writeEndDocument(); + return returnXML; +} + +XMLRPC::ResponseMessage::ResponseMessage( const QVariant & theValue ) +: MessageBase() +{ + m_values << theValue; +} diff --git a/source/xmlrpc.h b/source/xmlrpc.h new file mode 100644 index 00000000..c9585035 --- /dev/null +++ b/source/xmlrpc.h @@ -0,0 +1,184 @@ +#ifndef PACKET_H +#define PACKET_H +#include <QXmlStreamWriter> +#include <QVariant> +#include <QDomElement> +#include <QList> +/** +* Packets are serialized QVariants that map to XMLRPC types. +* @li int -> int +* @li double -> double,float +* @li string -> QString, QCString, char *, QColor, QFont +* @li datetime.iso8601 -> QDateTime, QTime, QDate +* @li array -> QList<QVariant>, QStringList +* @li struct -> QMap<QVariant> +* @li base64 -> QByteArray +* +* An example of how to use the XML Packets is as follows: +* @code +* QList<QVariant> args; +* args << m_db << m_username << m_password << dbQuery; +* RequestMessage msg( "data.query", args ); +* ResponseMessage resp( SomeHttpDispatchObject( msg.xml() ) ); +* if( resp.isValid() ) +* { +* int rows = resp.value().toMap()["widgets"].toInt(); +* } +* else +* qWarning("Error: %s", resp.error().latin1() ); +* @endcode +* This example will construct invoke the data.query() method on the XMLRPC +* interface with the args. It will then check for the response to see if +* it was valid. If its valid the message contains a struct of values, one of +* which is "widgets" that is an integer. The struct is converted to a QVariant +* map and we can convert it as such from the QVariant. We can then get the +* QVariant for the "widgets" value and convert that to an integer. If there was +* an error, the packet is marked invalid and will have an error message in it. The +* error() message will return this message. The struct in the value will be a valid +* error structure so it can be dealt with accordingly. +*/ + +namespace XMLRPC +{ +/** + * Base utility methods for working with XMLRPC messages. +@author Ian Reinhart Geiser <geiseri@kde.org> +*/ +class MessageBase +{ +public: + MessageBase(); + virtual ~MessageBase(); + + /** + * Returns a human readable error that was recived from the server. + */ + QString error() const; + + /** + * Sets the human readable error message. + */ + virtual void setError( const QString &message ) const; + + /** + * Returns if the current message is valid. + */ + bool isValid() const; + +protected: + virtual void marshall( QXmlStreamWriter *writer, const QVariant &val ) const; + virtual QVariant demarshall( const QDomElement &elem ) const; + + +private: + mutable QString m_message; + mutable bool m_valid; +}; + +/** +* Creates an XMLRPC message that will call a method with a series of +* QVariants that will be converted to XMLRPC types. +@author Ian Reinhart Geiser <geiseri@kde.org> +*/ +class RequestMessage : public MessageBase +{ +public: + RequestMessage( const QDomElement &element ); + /** + * Creates an RequestMessage from an XML packet. + */ + RequestMessage( const QByteArray &xml ); + /** + * Creates a method packet that will call method with a list of args. + */ + RequestMessage( const QByteArray &method, const QList<QVariant> &args ); + + /** + * Convinence ctor that creates a packet for a method with a single + * argument. + */ + RequestMessage( const QByteArray &method, const QVariant &arg ); + + virtual ~RequestMessage() {;} + + /** + * Return the xml representation of the packet. + */ + QByteArray xml() const; + void writeXml( QXmlStreamWriter *writer ) const; + + QByteArray method() const; + QList< QVariant > args() const; + + +private: + QByteArray m_method; + QList<QVariant> m_args; + +}; + +/** +* Decodes an XMLRPC message from a server into a set of QVariants. +@author Ian Reinhart Geiser <geiseri@kde.org> +*/ +class ResponseMessage : public MessageBase +{ +public: + /** + * Create a new recive packet with an xml packet + */ + ResponseMessage( const QDomElement &element ); + + /** + * Create a new recive packet with an xml packet + */ + ResponseMessage( const QByteArray &xml ); + /** + * Create a new response message with data. + */ + ResponseMessage( const QList< QVariant >& theValue ); + + /** + * Create a new response message with data. This is exactly like + * the above method save for it only takes a single QVariant. + */ + explicit ResponseMessage( const QVariant& theValue ); + + virtual ~ResponseMessage() {;} + /** + * Returns the number of data elements. Usually this is only + * 1 element, but some services will return mutiple messages in + * a single xmlrpc packet. + */ + int count() const; + /** + * Returns the native value for an xmlrpc message at an offset. + * Usually this method can be called with no arguments. + */ + QVariant value( int idx = 0 ) const; + + /** + * Return the xml representation of the packet. + */ + virtual QByteArray xml() const; + void writeXml( QXmlStreamWriter *writer ) const; + + + QList< QVariant > values() const; +protected: + void setValues( const QList<QVariant> va2ls); + +private: + QList<QVariant> m_values; +}; + +class FaultMessage : public ResponseMessage +{ +public: + FaultMessage( int code, const QString &message ); + QByteArray xml() const; + void writeXml( QXmlStreamWriter *writer ) const; + +}; +} +#endif |
