1/******************************************************************************
2 * Copyright (C) 2003 - 2004 by Frerich Raabe <raabe@kde.org> *
3 * Tobias Koenig <tokoe@kde.org> *
4 * Copyright (C) 2006 by Narayan Newton <narayannewton@gmail.com> *
5 * *
6 * This program is distributed in the hope that it will be useful, but *
7 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY *
8 * or FITNESS FOR A PARTICULAR PURPOSE. For licensing and distribution *
9 * details, check the accompanying file 'COPYING.BSD'. *
10 *****************************************************************************/
11/**
12 @file
13
14 This file is part of KXmlRpc and defines our internal classes.
15
16 @author Frerich Raabe <raabe@kde.org>
17 @author Tobias Koenig <tokoe@kde.org>
18 @author Narayan Newton <narayannewton@gmail.com>
19*/
20
21#include "query.h"
22
23#include <kdebug.h>
24#include <klocalizedstring.h>
25#include <kurl.h>
26
27#include <QtCore/QDateTime>
28#include <QtCore/QVariant>
29#include <QtXml/QDomDocument>
30
31using namespace KXmlRpc;
32
33/**
34 @file
35
36 Implementation of Query
37**/
38
39namespace KXmlRpc {
40
41/**
42 @brief
43 Result is an internal class that represents a response
44 from a XML-RPC server.
45
46 This is an internal class and is only used by Query.
47 @internal
48 */
49class Result
50{
51 friend class Query;
52 friend class Query::Private;
53
54 public:
55 /**
56 Constructs a result.
57 */
58 Result();
59
60 /**
61 Destroys a result.
62 */
63 ~Result();
64
65 /**
66 Returns true if the method call succeeded, false
67 if there was an XML-RPC fault.
68
69 @see errorCode(), errorString()
70 */
71 bool success() const;
72
73 /**
74 Returns the error code of the fault.
75
76 @see success(), errorString()
77 */
78 int errorCode() const;
79
80 /**
81 Returns the error string that describes the fault.
82
83 @see success, errorCode()
84 */
85 QString errorString() const;
86
87 /**
88 Returns the data sent to us from the server.
89 */
90 QList<QVariant> data() const;
91
92 private:
93 bool mSuccess;
94 int mErrorCode;
95 QString mErrorString;
96 QList<QVariant> mData;
97};
98
99} // namespace KXmlRpcClient
100
101KXmlRpc::Result::Result()
102{
103}
104
105KXmlRpc::Result::~Result()
106{
107}
108
109bool KXmlRpc::Result::success() const
110{
111 return mSuccess;
112}
113
114int KXmlRpc::Result::errorCode() const
115{
116 return mErrorCode;
117}
118
119QString KXmlRpc::Result::errorString() const
120{
121 return mErrorString;
122}
123
124QList<QVariant> KXmlRpc::Result::data() const
125{
126 return mData;
127}
128
129class Query::Private
130{
131 public:
132 Private( Query *parent )
133 : mParent( parent )
134 {
135 }
136
137 bool isMessageResponse( const QDomDocument &doc ) const;
138 bool isFaultResponse( const QDomDocument &doc ) const;
139
140 Result parseMessageResponse( const QDomDocument &doc ) const;
141 Result parseFaultResponse( const QDomDocument &doc ) const;
142
143 QString markupCall( const QString &method, const QList<QVariant> &args ) const;
144 QString marshal( const QVariant &value ) const;
145 QVariant demarshal( const QDomElement &element ) const;
146
147 void slotData( KIO::Job *job, const QByteArray &data );
148 void slotResult( KJob *job );
149
150 Query *mParent;
151 QByteArray mBuffer;
152 QVariant mId;
153 QList<KJob*> mPendingJobs;
154};
155
156bool Query::Private::isMessageResponse( const QDomDocument &doc ) const
157{
158 return doc.documentElement().firstChild().toElement().tagName().toLower()
159 == "params";
160}
161
162bool Query::Private::isFaultResponse( const QDomDocument &doc ) const
163{
164 return doc.documentElement().firstChild().toElement().tagName().toLower()
165 == "fault";
166}
167
168Result Query::Private::parseMessageResponse( const QDomDocument &doc ) const
169{
170 Result response;
171 response.mSuccess = true;
172
173 QDomNode paramNode = doc.documentElement().firstChild().firstChild();
174 while ( !paramNode.isNull() ) {
175 response.mData << demarshal( paramNode.firstChild().toElement() );
176 paramNode = paramNode.nextSibling();
177 }
178
179 return response;
180}
181
182Result Query::Private::parseFaultResponse( const QDomDocument &doc ) const
183{
184 Result response;
185 response.mSuccess = false;
186
187 QDomNode errorNode = doc.documentElement().firstChild().firstChild();
188 const QVariant errorVariant = demarshal( errorNode.toElement() );
189 response.mErrorCode = errorVariant.toMap() [ "faultCode" ].toInt();
190 response.mErrorString = errorVariant.toMap() [ "faultString" ].toString();
191
192 return response;
193}
194
195QString Query::Private::markupCall( const QString &cmd,
196 const QList<QVariant> &args ) const
197{
198 QString markup = "<?xml version=\"1.0\" ?>\r\n<methodCall>\r\n";
199
200 markup += "<methodName>" + cmd + "</methodName>\r\n";
201
202 if ( !args.isEmpty() ) {
203
204 markup += "<params>\r\n";
205 QList<QVariant>::ConstIterator it = args.begin();
206 QList<QVariant>::ConstIterator end = args.end();
207 for ( ; it != end; ++it ) {
208 markup += "<param>\r\n" + marshal( *it ) + "</param>\r\n";
209 }
210 markup += "</params>\r\n";
211 }
212
213 markup += "</methodCall>\r\n";
214
215 return markup;
216}
217
218QString Query::Private::marshal( const QVariant &arg ) const
219{
220 switch ( arg.type() ) {
221
222 case QVariant::String:
223 return "<value><string><![CDATA[" + arg.toString() + "]]></string></value>\r\n";
224 case QVariant::StringList:
225 {
226 QStringList data = arg.toStringList();
227 QStringListIterator dataIterator( data );
228 QString markup;
229 markup += "<value><array><data>";
230 while ( dataIterator.hasNext() ) {
231 markup += "<value><string><![CDATA[" + dataIterator.next() + "]]></string></value>\r\n";
232 }
233 markup += "</data></array></value>";
234 return markup;
235 }
236 case QVariant::Int:
237 return "<value><int>" + QString::number( arg.toInt() ) + "</int></value>\r\n";
238 case QVariant::Double:
239 return "<value><double>" + QString::number( arg.toDouble() ) + "</double></value>\r\n";
240 case QVariant::Bool:
241 {
242 QString markup = "<value><boolean>";
243 markup += arg.toBool() ? "1" : "0";
244 markup += "</boolean></value>\r\n";
245 return markup;
246 }
247 case QVariant::ByteArray:
248 return QByteArray(QByteArray("<value><base64>") + arg.toByteArray().toBase64() + QByteArray("</base64></value>\r\n"));
249 case QVariant::DateTime:
250 {
251 return "<value><dateTime.iso8601>" +
252 arg.toDateTime().toString( Qt::ISODate ) +
253 "</dateTime.iso8601></value>\r\n";
254 }
255 case QVariant::List:
256 {
257 QString markup = "<value><array><data>\r\n";
258 const QList<QVariant> args = arg.toList();
259 QList<QVariant>::ConstIterator it = args.begin();
260 QList<QVariant>::ConstIterator end = args.end();
261 for ( ; it != end; ++it ) {
262 markup += marshal( *it );
263 }
264 markup += "</data></array></value>\r\n";
265 return markup;
266 }
267 case QVariant::Map:
268 {
269 QString markup = "<value><struct>\r\n";
270 QMap<QString, QVariant> map = arg.toMap();
271 QMap<QString, QVariant>::ConstIterator it = map.constBegin();
272 QMap<QString, QVariant>::ConstIterator end = map.constEnd();
273 for ( ; it != end; ++it ) {
274 markup += "<member>\r\n";
275 markup += "<name>" + it.key() + "</name>\r\n";
276 markup += marshal( it.value() );
277 markup += "</member>\r\n";
278 }
279 markup += "</struct></value>\r\n";
280 return markup;
281 }
282 default:
283 kWarning() << "Failed to marshal unknown variant type:" << arg.type();
284 };
285
286 return QString();
287}
288
289QVariant Query::Private::demarshal( const QDomElement &element ) const
290{
291 Q_ASSERT( element.tagName().toLower() == "value" );
292
293 const QDomElement typeElement = element.firstChild().toElement();
294 const QString typeName = typeElement.tagName().toLower();
295
296 if ( typeName == "string" ) {
297 return QVariant( typeElement.text() );
298 } else if ( typeName == "i4" || typeName == "int" ) {
299 return QVariant( typeElement.text().toInt() );
300 } else if ( typeName == "double" ) {
301 return QVariant( typeElement.text().toDouble() );
302 } else if ( typeName == "boolean" ) {
303
304 if ( typeElement.text().toLower() == "true" || typeElement.text() == "1" ) {
305 return QVariant( true );
306 } else {
307 return QVariant( false );
308 }
309 } else if ( typeName == "base64" ) {
310 return QVariant( QByteArray::fromBase64( typeElement.text().toLatin1() ) );
311 } else if ( typeName == "datetime" || typeName == "datetime.iso8601" ) {
312 QDateTime date;
313 QString dateText = typeElement.text();
314 // Test for broken use of Basic ISO8601 date and extended ISO8601 time
315 if ( 17 <= dateText.length() && dateText.length() <= 18 &&
316 dateText.at( 4 ) != '-' && dateText.at( 11 ) == ':' ) {
317 if ( dateText.endsWith( 'Z' ) ) {
318 date = QDateTime::fromString( dateText, "yyyyMMddTHH:mm:ssZ" );
319 } else {
320 date = QDateTime::fromString( dateText, "yyyyMMddTHH:mm:ss" );
321 }
322 } else {
323 date = QDateTime::fromString( dateText, Qt::ISODate );
324 }
325 return QVariant( date );
326 } else if ( typeName == "array" ) {
327 QList<QVariant> values;
328 QDomNode valueNode = typeElement.firstChild().firstChild();
329 while ( !valueNode.isNull() ) {
330 values << demarshal( valueNode.toElement() );
331 valueNode = valueNode.nextSibling();
332 }
333 return QVariant( values );
334 } else if ( typeName == "struct" ) {
335
336 QMap<QString, QVariant> map;
337 QDomNode memberNode = typeElement.firstChild();
338 while ( !memberNode.isNull() ) {
339 const QString key = memberNode.toElement().elementsByTagName(
340 "name" ).item( 0 ).toElement().text();
341 const QVariant data = demarshal( memberNode.toElement().elementsByTagName(
342 "value" ).item( 0 ).toElement() );
343 map[ key ] = data;
344 memberNode = memberNode.nextSibling();
345 }
346 return QVariant( map );
347 } else {
348 kWarning() << "Cannot demarshal unknown type" << typeName;
349 }
350 return QVariant();
351}
352
353void Query::Private::slotData( KIO::Job *, const QByteArray &data )
354{
355 unsigned int oldSize = mBuffer.size();
356 mBuffer.resize( oldSize + data.size() );
357 memcpy( mBuffer.data() + oldSize, data.data(), data.size() );
358}
359
360void Query::Private::slotResult( KJob *job )
361{
362 mPendingJobs.removeAll( job );
363
364 if ( job->error() != 0 ) {
365 emit mParent->fault( job->error(), job->errorString(), mId );
366 emit mParent->finished( mParent );
367 return;
368 }
369
370 QDomDocument doc;
371 QString errMsg;
372 int errLine, errCol;
373 if ( !doc.setContent( mBuffer, false, &errMsg, &errLine, &errCol ) ) {
374 emit mParent->fault( -1, i18n( "Received invalid XML markup: %1 at %2:%3",
375 errMsg, errLine, errCol ), mId );
376 emit mParent->finished( mParent );
377 return;
378 }
379
380 mBuffer.truncate( 0 );
381
382 if ( isMessageResponse( doc ) ) {
383 emit mParent->message( parseMessageResponse( doc ).data(), mId );
384 } else if ( isFaultResponse( doc ) ) {
385 emit mParent->fault( parseFaultResponse( doc ).errorCode(),
386 parseFaultResponse( doc ).errorString(), mId );
387 } else {
388 emit mParent->fault( 1, i18n( "Unknown type of XML markup received" ),
389 mId );
390 }
391
392 emit mParent->finished( mParent );
393}
394
395Query *Query::create( const QVariant &id, QObject *parent )
396{
397 return new Query( id, parent );
398}
399
400void Query::call( const QString &server,
401 const QString &method,
402 const QList<QVariant> &args,
403 const QMap<QString, QString> &jobMetaData )
404{
405
406 const QString xmlMarkup = d->markupCall( method, args );
407
408 QMap<QString, QString>::const_iterator mapIter;
409 QByteArray postData;
410 QDataStream stream( &postData, QIODevice::WriteOnly );
411 stream.writeRawData( xmlMarkup.toUtf8(), xmlMarkup.toUtf8().length() );
412
413 KIO::TransferJob *job = KIO::http_post( KUrl( server ), postData, KIO::HideProgressInfo );
414
415 if ( !job ) {
416 kWarning() << "Unable to create KIO job for" << server;
417 return;
418 }
419
420 job->addMetaData( "content-type", "Content-Type: text/xml; charset=utf-8" );
421 job->addMetaData( "ConnectTimeout", "50" );
422
423 for ( mapIter = jobMetaData.begin(); mapIter != jobMetaData.end(); ++mapIter ) {
424 job->addMetaData( mapIter.key(), mapIter.value() );
425 }
426
427 connect( job, SIGNAL(data(KIO::Job*,QByteArray)),
428 this, SLOT(slotData(KIO::Job*,QByteArray)) );
429 connect( job, SIGNAL(result(KJob*)),
430 this, SLOT(slotResult(KJob*)) );
431
432 d->mPendingJobs.append( job );
433}
434
435Query::Query( const QVariant &id, QObject *parent )
436 : QObject( parent ), d( new Private( this ) )
437{
438 d->mId = id;
439}
440
441Query::~Query()
442{
443 QList<KJob*>::Iterator it;
444 for ( it = d->mPendingJobs.begin(); it != d->mPendingJobs.end(); ++it ) {
445 ( *it )->kill();
446 }
447 delete d;
448}
449
450#include "moc_query.cpp"
451
452