File indexing completed on 2024-04-28 15:32:50

0001 /*
0002     SPDX-FileCopyrightText: 2003-2004 Frerich Raabe <raabe@kde.org>
0003     SPDX-FileCopyrightText: 2003-2004 Tobias Koenig <tokoe@kde.org>
0004     SPDX-FileCopyrightText: 2006 Narayan Newton <narayannewton@gmail.com>
0005 
0006     SPDX-License-Identifier: BSD-2-Clause
0007 */
0008 /**
0009   @file
0010 
0011   This file is part of KXmlRpc and defines our internal classes.
0012 
0013   @author Frerich Raabe <raabe@kde.org>
0014   @author Tobias Koenig <tokoe@kde.org>
0015   @author Narayan Newton <narayannewton@gmail.com>
0016 */
0017 
0018 #include "query.h"
0019 #include "kxmlrpcclient_debug.h"
0020 #include "query_p.h"
0021 
0022 #include <KIO/Job>
0023 #include <klocalizedstring.h>
0024 
0025 using namespace KXmlRpc;
0026 
0027 /**
0028   @file
0029 
0030   Implementation of Query
0031 **/
0032 
0033 KXmlRpc::Result::Result()
0034     : mSuccess(false)
0035     , mErrorCode(-1)
0036 {
0037 }
0038 
0039 bool KXmlRpc::Result::success() const
0040 {
0041     return mSuccess;
0042 }
0043 
0044 int KXmlRpc::Result::errorCode() const
0045 {
0046     return mErrorCode;
0047 }
0048 
0049 QString KXmlRpc::Result::errorString() const
0050 {
0051     return mErrorString;
0052 }
0053 
0054 QList<QVariant> KXmlRpc::Result::data() const
0055 {
0056     return mData;
0057 }
0058 
0059 bool QueryPrivate::isMessageResponse(const QDomDocument &doc)
0060 {
0061     return doc.documentElement().firstChild().toElement().tagName().toLower() == QLatin1String("params");
0062 }
0063 
0064 bool QueryPrivate::isFaultResponse(const QDomDocument &doc)
0065 {
0066     return doc.documentElement().firstChild().toElement().tagName().toLower() == QLatin1String("fault");
0067 }
0068 
0069 Result QueryPrivate::parseMessageResponse(const QDomDocument &doc)
0070 {
0071     Result response;
0072     response.mSuccess = true;
0073 
0074     QDomNode paramNode = doc.documentElement().firstChild().firstChild();
0075     while (!paramNode.isNull()) {
0076         response.mData << demarshal(paramNode.firstChild().toElement());
0077         paramNode = paramNode.nextSibling();
0078     }
0079 
0080     return response;
0081 }
0082 
0083 Result QueryPrivate::parseFaultResponse(const QDomDocument &doc)
0084 {
0085     Result response;
0086     response.mSuccess = false;
0087 
0088     QDomNode errorNode = doc.documentElement().firstChild().firstChild();
0089     const QVariant errorVariant = demarshal(errorNode.toElement());
0090     response.mErrorCode = errorVariant.toMap()[QStringLiteral("faultCode")].toInt();
0091     response.mErrorString = errorVariant.toMap()[QStringLiteral("faultString")].toString();
0092 
0093     return response;
0094 }
0095 
0096 QByteArray QueryPrivate::markupCall(const QString &cmd, const QList<QVariant> &args)
0097 {
0098     QByteArray markup = "<?xml version=\"1.0\" ?>\r\n<methodCall>\r\n";
0099 
0100     markup += "<methodName>" + cmd.toLatin1() + "</methodName>\r\n";
0101 
0102     if (!args.isEmpty()) {
0103         markup += "<params>\r\n";
0104         for (auto it = args.constBegin(), end = args.constEnd(); it != end; ++it) {
0105             markup += "<param>\r\n" + marshal(*it) + "</param>\r\n";
0106         }
0107         markup += "</params>\r\n";
0108     }
0109 
0110     markup += "</methodCall>\r\n";
0111 
0112     return markup;
0113 }
0114 
0115 QByteArray QueryPrivate::marshal(const QVariant &arg)
0116 {
0117     switch (arg.type()) {
0118     case QVariant::String:
0119         return "<value><string><![CDATA[" + arg.toString().toUtf8() + "]]></string></value>\r\n";
0120     case QVariant::StringList: {
0121         QStringList data = arg.toStringList();
0122         QStringListIterator dataIterator(data);
0123         QByteArray markup;
0124         markup += "<value><array><data>";
0125         while (dataIterator.hasNext()) {
0126             markup += "<value><string><![CDATA[" + dataIterator.next().toUtf8() + "]]></string></value>\r\n";
0127         }
0128         markup += "</data></array></value>";
0129         return markup;
0130     }
0131     case QVariant::Int:
0132         return "<value><int>" + QByteArray::number(arg.toInt()) + "</int></value>\r\n";
0133     case QVariant::Double:
0134         return "<value><double>" + QByteArray::number(arg.toDouble()) + "</double></value>\r\n";
0135     case QVariant::Bool:
0136         return "<value><boolean>" + QByteArray(arg.toBool() ? "1" : "0") + "</boolean></value>\r\n";
0137     case QVariant::ByteArray:
0138         return "<value><base64>" + arg.toByteArray().toBase64() + "</base64></value>\r\n";
0139     case QVariant::DateTime:
0140         return "<value><dateTime.iso8601>" + arg.toDateTime().toString(Qt::ISODate).toLatin1() + "</dateTime.iso8601></value>\r\n";
0141     case QVariant::List: {
0142         QByteArray markup = "<value><array><data>\r\n";
0143         const QList<QVariant> args = arg.toList();
0144         QList<QVariant>::ConstIterator it = args.begin();
0145         QList<QVariant>::ConstIterator end = args.end();
0146         for (; it != end; ++it) {
0147             markup += marshal(*it);
0148         }
0149         markup += "</data></array></value>\r\n";
0150         return markup;
0151     }
0152     case QVariant::Map: {
0153         QByteArray markup = "<value><struct>\r\n";
0154         const QMap<QString, QVariant> map = arg.toMap();
0155         for (auto it = map.constBegin(), end = map.constEnd(); it != end; ++it) {
0156             markup += "<member>\r\n";
0157             markup += "<name>" + it.key().toUtf8() + "</name>\r\n";
0158             markup += marshal(it.value());
0159             markup += "</member>\r\n";
0160         }
0161         markup += "</struct></value>\r\n";
0162         return markup;
0163     }
0164     default:
0165         qCWarning(KXMLRPCCLIENT_LOG) << "Failed to marshal unknown variant type:" << arg.type();
0166     };
0167 
0168     return QByteArray();
0169 }
0170 
0171 QVariant QueryPrivate::demarshal(const QDomElement &element)
0172 {
0173     Q_ASSERT(element.tagName().toLower() == QLatin1String("value"));
0174 
0175     const QDomElement typeElement = element.firstChild().toElement();
0176     const QString typeName = typeElement.tagName().toLower();
0177 
0178     if (typeName == QLatin1String("string")) {
0179         return QVariant(typeElement.text());
0180     } else if (typeName == QLatin1String("i4") || typeName == QLatin1String("int")) {
0181         return QVariant(typeElement.text().toInt());
0182     } else if (typeName == QLatin1String("double")) {
0183         return QVariant(typeElement.text().toDouble());
0184     } else if (typeName == QLatin1String("boolean")) {
0185         if (typeElement.text().toLower() == QLatin1String("true") || typeElement.text() == QLatin1String("1")) {
0186             return QVariant(true);
0187         } else {
0188             return QVariant(false);
0189         }
0190     } else if (typeName == QLatin1String("base64")) {
0191         return QVariant(QByteArray::fromBase64(typeElement.text().toLatin1()));
0192     } else if (typeName == QLatin1String("datetime") || typeName == QLatin1String("datetime.iso8601")) {
0193         QDateTime date;
0194         QString dateText = typeElement.text();
0195         // Test for broken use of Basic ISO8601 date and extended ISO8601 time
0196         if (17 <= dateText.length() && dateText.length() <= 18 //
0197             && dateText.at(4) != QLatin1Char('-') && dateText.at(11) == QLatin1Char(':')) {
0198             if (dateText.endsWith(QLatin1Char('Z'))) {
0199                 date = QDateTime::fromString(dateText, QStringLiteral("yyyyMMddTHH:mm:ssZ"));
0200             } else {
0201                 date = QDateTime::fromString(dateText, QStringLiteral("yyyyMMddTHH:mm:ss"));
0202             }
0203         } else {
0204             date = QDateTime::fromString(dateText, Qt::ISODate);
0205         }
0206         return QVariant(date);
0207     } else if (typeName == QLatin1String("array")) {
0208         QList<QVariant> values;
0209         QDomNode valueNode = typeElement.firstChild().firstChild();
0210         while (!valueNode.isNull()) {
0211             values << demarshal(valueNode.toElement());
0212             valueNode = valueNode.nextSibling();
0213         }
0214         return QVariant(values);
0215     } else if (typeName == QLatin1String("struct")) {
0216         QMap<QString, QVariant> map;
0217         QDomNode memberNode = typeElement.firstChild();
0218         while (!memberNode.isNull()) {
0219             const QString key = memberNode.toElement().elementsByTagName(QStringLiteral("name")).item(0).toElement().text();
0220             const QVariant data = demarshal(memberNode.toElement().elementsByTagName(QStringLiteral("value")).item(0).toElement());
0221             map[key] = data;
0222             memberNode = memberNode.nextSibling();
0223         }
0224         return QVariant(map);
0225     } else {
0226         qCWarning(KXMLRPCCLIENT_LOG) << "Cannot demarshal unknown type" << typeName;
0227     }
0228     return QVariant();
0229 }
0230 
0231 void QueryPrivate::slotData(KIO::Job *, const QByteArray &data)
0232 {
0233     unsigned int oldSize = mBuffer.size();
0234     mBuffer.resize(oldSize + data.size());
0235     memcpy(mBuffer.data() + oldSize, data.data(), data.size());
0236 }
0237 
0238 void QueryPrivate::slotResult(KJob *job)
0239 {
0240     mPendingJobs.removeAll(job);
0241 
0242     if (job->error() != 0) {
0243         Q_EMIT mParent->fault(job->error(), job->errorString(), mId);
0244         Q_EMIT mParent->finished(mParent);
0245         return;
0246     }
0247 
0248     QDomDocument doc;
0249     QString errMsg;
0250     int errLine;
0251     int errCol;
0252     if (!doc.setContent(mBuffer, false, &errMsg, &errLine, &errCol)) {
0253         Q_EMIT mParent->fault(-1, i18n("Received invalid XML markup: %1 at %2:%3", errMsg, errLine, errCol), mId);
0254         Q_EMIT mParent->finished(mParent);
0255         return;
0256     }
0257 
0258     mBuffer.truncate(0);
0259 
0260     if (isMessageResponse(doc)) {
0261         Q_EMIT mParent->message(parseMessageResponse(doc).data(), mId);
0262     } else if (isFaultResponse(doc)) {
0263         const Result fault = parseFaultResponse(doc);
0264         Q_EMIT mParent->fault(fault.errorCode(), fault.errorString(), mId);
0265     } else {
0266         Q_EMIT mParent->fault(1, i18n("Unknown type of XML markup received"), mId);
0267     }
0268 
0269     Q_EMIT mParent->finished(mParent);
0270 }
0271 
0272 Query *Query::create(const QVariant &id, QObject *parent)
0273 {
0274     return new Query(id, parent);
0275 }
0276 
0277 void Query::call(const QUrl &server, const QString &method, const QList<QVariant> &args, const QMap<QString, QString> &jobMetaData)
0278 {
0279     const QByteArray xmlMarkup = d->markupCall(method, args);
0280     KIO::TransferJob *job = KIO::http_post(server, xmlMarkup, KIO::HideProgressInfo);
0281 
0282     if (!job) {
0283         qCWarning(KXMLRPCCLIENT_LOG) << "Unable to create KIO job for" << server.url();
0284         return;
0285     }
0286 
0287     job->addMetaData(QStringLiteral("content-type"), QStringLiteral("Content-Type: text/xml; charset=utf-8"));
0288     job->addMetaData(QStringLiteral("ConnectTimeout"), QStringLiteral("50"));
0289 
0290     for (auto it = jobMetaData.begin(), end = jobMetaData.end(); it != end; ++it) {
0291         job->addMetaData(it.key(), it.value());
0292     }
0293 
0294     connect(job, SIGNAL(data(KIO::Job *, QByteArray)), this, SLOT(slotData(KIO::Job *, QByteArray)));
0295     connect(job, SIGNAL(result(KJob *)), this, SLOT(slotResult(KJob *)));
0296 
0297     d->mPendingJobs.append(job);
0298 }
0299 
0300 Query::Query(const QVariant &id, QObject *parent)
0301     : QObject(parent)
0302     , d(new QueryPrivate(this))
0303 {
0304     d->mId = id;
0305 }
0306 
0307 Query::~Query()
0308 {
0309     for (auto it = d->mPendingJobs.begin(), end = d->mPendingJobs.end(); it != end; ++it) {
0310         (*it)->kill();
0311     }
0312     delete d;
0313 }
0314 
0315 #include "moc_query.cpp"