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"