/****************************************************************************** * Copyright (C) 2003 - 2004 by Frerich Raabe * * Tobias Koenig * * Copyright (C) 2006 by Narayan Newton * * * * 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. For licensing and distribution * * details, check the accompanying file 'COPYING.BSD'. * *****************************************************************************/ /** @file This file is part of KXmlRpc and defines our internal classes. @author Frerich Raabe @author Tobias Koenig @author Narayan Newton */ #include "query.h" #include #include #include #include #include #include using namespace KXmlRpc; /** @file Implementation of Query **/ namespace KXmlRpc { /** @brief Result is an internal class that represents a response from a XML-RPC server. This is an internal class and is only used by Query. @internal */ class Result { friend class Query; friend class Query::Private; public: /** Constructs a result. */ Result(); /** Destroys a result. */ ~Result(); /** Returns true if the method call succeeded, false if there was an XML-RPC fault. @see errorCode(), errorString() */ bool success() const; /** Returns the error code of the fault. @see success(), errorString() */ int errorCode() const; /** Returns the error string that describes the fault. @see success, errorCode() */ QString errorString() const; /** Returns the data sent to us from the server. */ QList data() const; private: bool mSuccess; int mErrorCode; QString mErrorString; QList mData; }; } // namespace KXmlRpcClient KXmlRpc::Result::Result() { } KXmlRpc::Result::~Result() { } bool KXmlRpc::Result::success() const { return mSuccess; } int KXmlRpc::Result::errorCode() const { return mErrorCode; } QString KXmlRpc::Result::errorString() const { return mErrorString; } QList KXmlRpc::Result::data() const { return mData; } class Query::Private { public: Private( Query *parent ) : mParent( parent ) { } bool isMessageResponse( const QDomDocument &doc ) const; bool isFaultResponse( const QDomDocument &doc ) const; Result parseMessageResponse( const QDomDocument &doc ) const; Result parseFaultResponse( const QDomDocument &doc ) const; QString markupCall( const QString &method, const QList &args ) const; QString marshal( const QVariant &value ) const; QVariant demarshal( const QDomElement &element ) const; void slotData( KIO::Job *job, const QByteArray &data ); void slotResult( KJob *job ); Query *mParent; QByteArray mBuffer; QVariant mId; QList mPendingJobs; }; bool Query::Private::isMessageResponse( const QDomDocument &doc ) const { return doc.documentElement().firstChild().toElement().tagName().toLower() == "params"; } bool Query::Private::isFaultResponse( const QDomDocument &doc ) const { return doc.documentElement().firstChild().toElement().tagName().toLower() == "fault"; } Result Query::Private::parseMessageResponse( const QDomDocument &doc ) const { Result response; response.mSuccess = true; QDomNode paramNode = doc.documentElement().firstChild().firstChild(); while ( !paramNode.isNull() ) { response.mData << demarshal( paramNode.firstChild().toElement() ); paramNode = paramNode.nextSibling(); } return response; } Result Query::Private::parseFaultResponse( const QDomDocument &doc ) const { Result response; response.mSuccess = false; QDomNode errorNode = doc.documentElement().firstChild().firstChild(); const QVariant errorVariant = demarshal( errorNode.toElement() ); response.mErrorCode = errorVariant.toMap() [ "faultCode" ].toInt(); response.mErrorString = errorVariant.toMap() [ "faultString" ].toString(); return response; } QString Query::Private::markupCall( const QString &cmd, const QList &args ) const { QString markup = "\r\n\r\n"; markup += "" + cmd + "\r\n"; if ( !args.isEmpty() ) { markup += "\r\n"; QList::ConstIterator it = args.begin(); QList::ConstIterator end = args.end(); for ( ; it != end; ++it ) { markup += "\r\n" + marshal( *it ) + "\r\n"; } markup += "\r\n"; } markup += "\r\n"; return markup; } QString Query::Private::marshal( const QVariant &arg ) const { switch ( arg.type() ) { case QVariant::String: return "\r\n"; case QVariant::StringList: { QStringList data = arg.toStringList(); QStringListIterator dataIterator( data ); QString markup; markup += ""; while ( dataIterator.hasNext() ) { markup += "\r\n"; } markup += ""; return markup; } case QVariant::Int: return "" + QString::number( arg.toInt() ) + "\r\n"; case QVariant::Double: return "" + QString::number( arg.toDouble() ) + "\r\n"; case QVariant::Bool: { QString markup = ""; markup += arg.toBool() ? "1" : "0"; markup += "\r\n"; return markup; } case QVariant::ByteArray: return QByteArray(QByteArray("") + arg.toByteArray().toBase64() + QByteArray("\r\n")); case QVariant::DateTime: { return "" + arg.toDateTime().toString( Qt::ISODate ) + "\r\n"; } case QVariant::List: { QString markup = "\r\n"; const QList args = arg.toList(); QList::ConstIterator it = args.begin(); QList::ConstIterator end = args.end(); for ( ; it != end; ++it ) { markup += marshal( *it ); } markup += "\r\n"; return markup; } case QVariant::Map: { QString markup = "\r\n"; QMap map = arg.toMap(); QMap::ConstIterator it = map.constBegin(); QMap::ConstIterator end = map.constEnd(); for ( ; it != end; ++it ) { markup += "\r\n"; markup += "" + it.key() + "\r\n"; markup += marshal( it.value() ); markup += "\r\n"; } markup += "\r\n"; return markup; } default: kWarning() << "Failed to marshal unknown variant type:" << arg.type(); }; return QString(); } QVariant Query::Private::demarshal( const QDomElement &element ) const { Q_ASSERT( element.tagName().toLower() == "value" ); const QDomElement typeElement = element.firstChild().toElement(); const QString typeName = typeElement.tagName().toLower(); if ( typeName == "string" ) { return QVariant( typeElement.text() ); } else if ( typeName == "i4" || typeName == "int" ) { return QVariant( typeElement.text().toInt() ); } else if ( typeName == "double" ) { return QVariant( typeElement.text().toDouble() ); } else if ( typeName == "boolean" ) { if ( typeElement.text().toLower() == "true" || typeElement.text() == "1" ) { return QVariant( true ); } else { return QVariant( false ); } } else if ( typeName == "base64" ) { return QVariant( QByteArray::fromBase64( typeElement.text().toLatin1() ) ); } else if ( typeName == "datetime" || typeName == "datetime.iso8601" ) { QDateTime date; QString dateText = typeElement.text(); // Test for broken use of Basic ISO8601 date and extended ISO8601 time if ( 17 <= dateText.length() && dateText.length() <= 18 && dateText.at( 4 ) != '-' && dateText.at( 11 ) == ':' ) { if ( dateText.endsWith( 'Z' ) ) { date = QDateTime::fromString( dateText, "yyyyMMddTHH:mm:ssZ" ); } else { date = QDateTime::fromString( dateText, "yyyyMMddTHH:mm:ss" ); } } else { date = QDateTime::fromString( dateText, Qt::ISODate ); } return QVariant( date ); } else if ( typeName == "array" ) { QList values; QDomNode valueNode = typeElement.firstChild().firstChild(); while ( !valueNode.isNull() ) { values << demarshal( valueNode.toElement() ); valueNode = valueNode.nextSibling(); } return QVariant( values ); } else if ( typeName == "struct" ) { QMap map; QDomNode memberNode = typeElement.firstChild(); while ( !memberNode.isNull() ) { const QString key = memberNode.toElement().elementsByTagName( "name" ).item( 0 ).toElement().text(); const QVariant data = demarshal( memberNode.toElement().elementsByTagName( "value" ).item( 0 ).toElement() ); map[ key ] = data; memberNode = memberNode.nextSibling(); } return QVariant( map ); } else { kWarning() << "Cannot demarshal unknown type" << typeName; } return QVariant(); } void Query::Private::slotData( KIO::Job *, const QByteArray &data ) { unsigned int oldSize = mBuffer.size(); mBuffer.resize( oldSize + data.size() ); memcpy( mBuffer.data() + oldSize, data.data(), data.size() ); } void Query::Private::slotResult( KJob *job ) { mPendingJobs.removeAll( job ); if ( job->error() != 0 ) { emit mParent->fault( job->error(), job->errorString(), mId ); emit mParent->finished( mParent ); return; } QDomDocument doc; QString errMsg; int errLine, errCol; if ( !doc.setContent( mBuffer, false, &errMsg, &errLine, &errCol ) ) { emit mParent->fault( -1, i18n( "Received invalid XML markup: %1 at %2:%3", errMsg, errLine, errCol ), mId ); emit mParent->finished( mParent ); return; } mBuffer.truncate( 0 ); if ( isMessageResponse( doc ) ) { emit mParent->message( parseMessageResponse( doc ).data(), mId ); } else if ( isFaultResponse( doc ) ) { emit mParent->fault( parseFaultResponse( doc ).errorCode(), parseFaultResponse( doc ).errorString(), mId ); } else { emit mParent->fault( 1, i18n( "Unknown type of XML markup received" ), mId ); } emit mParent->finished( mParent ); } Query *Query::create( const QVariant &id, QObject *parent ) { return new Query( id, parent ); } void Query::call( const QString &server, const QString &method, const QList &args, const QMap &jobMetaData ) { const QString xmlMarkup = d->markupCall( method, args ); QMap::const_iterator mapIter; QByteArray postData; QDataStream stream( &postData, QIODevice::WriteOnly ); stream.writeRawData( xmlMarkup.toUtf8(), xmlMarkup.toUtf8().length() ); KIO::TransferJob *job = KIO::http_post( KUrl( server ), postData, KIO::HideProgressInfo ); if ( !job ) { kWarning() << "Unable to create KIO job for" << server; return; } job->addMetaData( "content-type", "Content-Type: text/xml; charset=utf-8" ); job->addMetaData( "ConnectTimeout", "50" ); for ( mapIter = jobMetaData.begin(); mapIter != jobMetaData.end(); ++mapIter ) { job->addMetaData( mapIter.key(), mapIter.value() ); } connect( job, SIGNAL(data(KIO::Job*,QByteArray)), this, SLOT(slotData(KIO::Job*,QByteArray)) ); connect( job, SIGNAL(result(KJob*)), this, SLOT(slotResult(KJob*)) ); d->mPendingJobs.append( job ); } Query::Query( const QVariant &id, QObject *parent ) : QObject( parent ), d( new Private( this ) ) { d->mId = id; } Query::~Query() { QList::Iterator it; for ( it = d->mPendingJobs.begin(); it != d->mPendingJobs.end(); ++it ) { ( *it )->kill(); } delete d; } #include "moc_query.cpp"