File indexing completed on 2024-12-22 04:57:03

0001 /*
0002     SPDX-FileCopyrightText: 2015-2019 Krzysztof Nowicki <krissn@op.pl>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "ewsrequest.h"
0008 
0009 #include <QTemporaryFile>
0010 
0011 #include "auth/ewsabstractauth.h"
0012 #include "ewsclient_debug.h"
0013 
0014 EwsRequest::EwsRequest(EwsClient &client, QObject *parent)
0015     : EwsJob(parent)
0016     , mClient(client)
0017     , mServerVersion(EwsServerVersion::ewsVersion2007Sp1)
0018 {
0019 }
0020 
0021 EwsRequest::~EwsRequest() = default;
0022 
0023 void EwsRequest::doSend()
0024 {
0025     const auto jobs{subjobs()};
0026     for (KJob *job : jobs) {
0027         job->start();
0028     }
0029 }
0030 
0031 void EwsRequest::startSoapDocument(QXmlStreamWriter &writer)
0032 {
0033     writer.writeStartDocument();
0034 
0035     writer.writeNamespace(soapEnvNsUri, QStringLiteral("soap"));
0036     writer.writeNamespace(ewsMsgNsUri, QStringLiteral("m"));
0037     writer.writeNamespace(ewsTypeNsUri, QStringLiteral("t"));
0038 
0039     // SOAP Envelope
0040     writer.writeStartElement(soapEnvNsUri, QStringLiteral("Envelope"));
0041 
0042     // SOAP Header
0043     writer.writeStartElement(soapEnvNsUri, QStringLiteral("Header"));
0044     mServerVersion.writeRequestServerVersion(writer);
0045     writer.writeEndElement();
0046 
0047     // SOAP Body
0048     writer.writeStartElement(soapEnvNsUri, QStringLiteral("Body"));
0049 }
0050 
0051 void EwsRequest::endSoapDocument(QXmlStreamWriter &writer)
0052 {
0053     // End SOAP Body
0054     writer.writeEndElement();
0055 
0056     // End SOAP Envelope
0057     writer.writeEndElement();
0058 
0059     writer.writeEndDocument();
0060 }
0061 
0062 void EwsRequest::prepare(const QString &body)
0063 {
0064     mBody = body;
0065 
0066     QString username, password;
0067     QStringList customHeaders;
0068     if (mClient.auth()) {
0069         if (!mClient.auth()->getAuthData(username, password, customHeaders)) {
0070             setErrorMsg(QStringLiteral("Failed to retrieve authentication data"));
0071         }
0072     }
0073 
0074     QUrl url = mClient.url();
0075     url.setUserName(username);
0076     url.setPassword(password);
0077 
0078     KIO::TransferJob *job = KIO::http_post(url, body.toUtf8(), KIO::HideProgressInfo);
0079     job->addMetaData(QStringLiteral("content-type"), QStringLiteral("text/xml"));
0080     if (!mClient.userAgent().isEmpty()) {
0081         job->addMetaData(QStringLiteral("UserAgent"), mClient.userAgent());
0082     }
0083 
0084     job->addMetaData(mMd);
0085 
0086     if (!customHeaders.isEmpty()) {
0087         job->addMetaData(QStringLiteral("customHTTPHeader"), customHeaders.join(QLatin1StringView("\r\n")));
0088     }
0089 
0090     job->addMetaData(QStringLiteral("no-auth-prompt"), QStringLiteral("true"));
0091     if (mClient.isNTLMv2Enabled()) {
0092         job->addMetaData(QStringLiteral("EnableNTLMv2Auth"), QStringLiteral("true"));
0093     }
0094 
0095     connect(job, &KIO::TransferJob::result, this, &EwsRequest::requestResult);
0096     connect(job, &KIO::TransferJob::data, this, &EwsRequest::requestData);
0097 
0098     addSubjob(job);
0099 }
0100 
0101 void EwsRequest::setMetaData(const KIO::MetaData &md)
0102 {
0103     mMd = md;
0104 }
0105 
0106 void EwsRequest::addMetaData(const QString &key, const QString &value)
0107 {
0108     mMd.insert(key, value);
0109 }
0110 
0111 void EwsRequest::requestResult(KJob *job)
0112 {
0113     if (EWSCLI_PROTO_LOG().isDebugEnabled()) {
0114         ewsLogDir.setAutoRemove(false);
0115         if (ewsLogDir.isValid()) {
0116             QTemporaryFile dumpFile(ewsLogDir.path() + QStringLiteral("/ews_xmldump_XXXXXXX.xml"));
0117             dumpFile.open();
0118             dumpFile.setAutoRemove(false);
0119             dumpFile.write(mResponseData.toUtf8());
0120             qCDebug(EWSCLI_PROTO_LOG) << "response dumped to" << dumpFile.fileName();
0121             dumpFile.close();
0122         }
0123     }
0124 
0125     auto trJob = qobject_cast<KIO::TransferJob *>(job);
0126     int resp = trJob->metaData()[QStringLiteral("responsecode")].toUInt();
0127 
0128     if (resp == 401 && mClient.auth()) {
0129         mClient.auth()->notifyRequestAuthFailed();
0130         setEwsResponseCode(EwsResponseCodeUnauthorized);
0131     }
0132 
0133     if (job->error() != 0) {
0134         setErrorMsg(QStringLiteral("Failed to process EWS request: ") + job->errorString(), job->error());
0135     }
0136     /* Don't attempt to parse the response in case of a HTTP error. The only exception is
0137      * 500 (Bad Request) as in such case the server does provide the usual SOAP response. */
0138     else if ((resp >= 300) && (resp != 500)) {
0139         setErrorMsg(QStringLiteral("Failed to process EWS request - HTTP code %1").arg(resp));
0140         setError(resp);
0141     } else {
0142         QXmlStreamReader reader(mResponseData);
0143         readResponse(reader);
0144     }
0145 
0146     emitResult();
0147 }
0148 
0149 bool EwsRequest::readResponse(QXmlStreamReader &reader)
0150 {
0151     if (!reader.readNextStartElement()) {
0152         return setErrorMsg(QStringLiteral("Failed to read EWS request XML"));
0153     }
0154 
0155     if ((reader.name() != QLatin1StringView("Envelope")) || (reader.namespaceUri() != soapEnvNsUri)) {
0156         return setErrorMsg(QStringLiteral("Failed to read EWS request - not a SOAP XML"));
0157     }
0158 
0159     while (reader.readNextStartElement()) {
0160         if (reader.namespaceUri() != soapEnvNsUri) {
0161             return setErrorMsg(QStringLiteral("Failed to read EWS request - not a SOAP XML"));
0162         }
0163 
0164         if (reader.name() == QLatin1StringView("Body")) {
0165             if (!readSoapBody(reader)) {
0166                 return false;
0167             }
0168         } else if (reader.name() == QLatin1StringView("Header")) {
0169             if (!readHeader(reader)) {
0170                 return false;
0171             }
0172         }
0173     }
0174     return true;
0175 }
0176 
0177 bool EwsRequest::readSoapBody(QXmlStreamReader &reader)
0178 {
0179     while (reader.readNextStartElement()) {
0180         if ((reader.name() == QLatin1StringView("Fault")) && (reader.namespaceUri() == soapEnvNsUri)) {
0181             return readSoapFault(reader);
0182         }
0183 
0184         if (!parseResult(reader)) {
0185             if (EWSCLI_FAILEDREQUEST_LOG().isDebugEnabled()) {
0186                 dump();
0187             }
0188             return false;
0189         }
0190     }
0191     return true;
0192 }
0193 QPair<QStringView, QString> EwsRequest::parseNamespacedString(const QString &str, const QXmlStreamNamespaceDeclarations &namespaces)
0194 {
0195     const auto tokens = str.split(QLatin1Char(':'));
0196     switch (tokens.count()) {
0197     case 1:
0198         return {QStringView(), str};
0199     case 2:
0200         for (const auto &ns : namespaces) {
0201             if (ns.prefix() == tokens[0]) {
0202                 return {ns.namespaceUri(), tokens[1]};
0203             }
0204         }
0205         /* fall through */
0206     default:
0207         return {};
0208     }
0209 }
0210 EwsResponseCode EwsRequest::parseEwsResponseCode(const QPair<QStringView, QString> &code)
0211 
0212 {
0213     if (code.first == ewsTypeNsUri) {
0214         return decodeEwsResponseCode(code.second);
0215     } else {
0216         return EwsResponseCodeUnknown;
0217     }
0218 }
0219 
0220 bool EwsRequest::readSoapFault(QXmlStreamReader &reader)
0221 {
0222     QString faultCode;
0223     QString faultString;
0224     while (reader.readNextStartElement()) {
0225         if (reader.name() == QLatin1StringView("faultcode")) {
0226             const auto rawCode = reader.readElementText();
0227             const auto parsedCode = parseEwsResponseCode(parseNamespacedString(rawCode, reader.namespaceDeclarations()));
0228             if (parsedCode != EwsResponseCodeUnknown) {
0229                 setEwsResponseCode(parsedCode);
0230             }
0231             faultCode = rawCode;
0232         } else if (reader.name() == QLatin1StringView("faultstring")) {
0233             faultString = reader.readElementText();
0234         }
0235     }
0236 
0237     qCWarning(EWSCLI_LOG) << "readSoapFault" << faultCode;
0238 
0239     setErrorMsg(faultCode + QStringLiteral(": ") + faultString);
0240 
0241     if (EWSCLI_FAILEDREQUEST_LOG().isDebugEnabled()) {
0242         dump();
0243     }
0244 
0245     return false;
0246 }
0247 
0248 void EwsRequest::requestData(KIO::Job *job, const QByteArray &data)
0249 {
0250     Q_UNUSED(job)
0251 
0252     qCDebug(EWSCLI_PROTO_LOG) << "data" << job << data;
0253     mResponseData += QString::fromUtf8(data);
0254 }
0255 
0256 bool EwsRequest::parseResponseMessage(QXmlStreamReader &reader, const QString &reqName, ContentReaderFn contentReader)
0257 {
0258     if (reader.name().toString() != reqName + QStringLiteral("Response") || reader.namespaceUri() != ewsMsgNsUri) {
0259         return setErrorMsg(QStringLiteral("Failed to read EWS request - expected %1 element.").arg(reqName + QStringLiteral("Response")));
0260     }
0261 
0262     if (!reader.readNextStartElement()) {
0263         return setErrorMsg(QStringLiteral("Failed to read EWS request - expected a child element in %1 element.").arg(reqName + QStringLiteral("Response")));
0264     }
0265 
0266     if (reader.name().toString() != QLatin1StringView("ResponseMessages") || reader.namespaceUri() != ewsMsgNsUri) {
0267         return setErrorMsg(QStringLiteral("Failed to read EWS request - expected %1 element.").arg(QStringLiteral("ResponseMessages")));
0268     }
0269 
0270     while (reader.readNextStartElement()) {
0271         if (reader.name().toString() != reqName + QStringLiteral("ResponseMessage") || reader.namespaceUri() != ewsMsgNsUri) {
0272             return setErrorMsg(QStringLiteral("Failed to read EWS request - expected %1 element.").arg(reqName + QStringLiteral("ResponseMessage")));
0273         }
0274 
0275         if (!contentReader(reader)) {
0276             return false;
0277         }
0278     }
0279 
0280     return true;
0281 }
0282 
0283 void EwsRequest::setServerVersion(const EwsServerVersion &version)
0284 {
0285     mServerVersion = version;
0286 }
0287 
0288 EwsRequest::Response::Response(QXmlStreamReader &reader)
0289 {
0290     static const QString respClasses[] = {
0291         QStringLiteral("Success"),
0292         QStringLiteral("Warning"),
0293         QStringLiteral("Error"),
0294     };
0295 
0296     auto respClassRef = reader.attributes().value(QStringLiteral("ResponseClass"));
0297     if (respClassRef.isNull()) {
0298         mClass = EwsResponseParseError;
0299         qCWarning(EWSCLI_LOG) << "ResponseClass attribute not found in response element";
0300         return;
0301     }
0302 
0303     unsigned i;
0304     for (i = 0; i < sizeof(respClasses) / sizeof(respClasses[0]); ++i) {
0305         if (respClassRef == respClasses[i]) {
0306             mClass = static_cast<EwsResponseClass>(i);
0307             break;
0308         }
0309     }
0310 }
0311 
0312 bool EwsRequest::Response::readResponseElement(QXmlStreamReader &reader)
0313 {
0314     if (reader.namespaceUri() != ewsMsgNsUri) {
0315         return false;
0316     }
0317     if (reader.name() == QLatin1StringView("ResponseCode")) {
0318         mCode = reader.readElementText();
0319     } else if (reader.name() == QLatin1StringView("MessageText")) {
0320         mMessage = reader.readElementText();
0321     } else if (reader.name() == QLatin1StringView("DescriptiveLinkKey")) {
0322         reader.skipCurrentElement();
0323     } else if (reader.name() == QLatin1StringView("MessageXml")) {
0324         reader.skipCurrentElement();
0325     } else if (reader.name() == QLatin1StringView("ErrorSubscriptionIds")) {
0326         reader.skipCurrentElement();
0327     } else {
0328         return false;
0329     }
0330     return true;
0331 }
0332 
0333 bool EwsRequest::readHeader(QXmlStreamReader &reader)
0334 {
0335     while (reader.readNextStartElement()) {
0336         if (reader.name() == QLatin1StringView("ServerVersionInfo") && reader.namespaceUri() == ewsTypeNsUri) {
0337             EwsServerVersion version(reader);
0338             if (!version.isValid()) {
0339                 qCWarningNC(EWSCLI_LOG) << QStringLiteral("Failed to read EWS request - error parsing server version.");
0340                 return false;
0341             }
0342             mServerVersion = version;
0343             mClient.setServerVersion(version);
0344             reader.skipCurrentElement();
0345         } else {
0346             reader.skipCurrentElement();
0347         }
0348     }
0349 
0350     return true;
0351 }
0352 
0353 bool EwsRequest::Response::setErrorMsg(const QString &msg)
0354 {
0355     mClass = EwsResponseParseError;
0356     mCode = QStringLiteral("ResponseParseError");
0357     mMessage = msg;
0358     qCWarningNC(EWSCLI_LOG) << msg;
0359     return false;
0360 }
0361 
0362 void EwsRequest::dump() const
0363 {
0364     ewsLogDir.setAutoRemove(false);
0365     if (ewsLogDir.isValid()) {
0366         QTemporaryFile reqDumpFile(ewsLogDir.path() + QStringLiteral("/ews_xmlreqdump_XXXXXXX.xml"));
0367         reqDumpFile.open();
0368         reqDumpFile.setAutoRemove(false);
0369         reqDumpFile.write(mBody.toUtf8());
0370         reqDumpFile.close();
0371         QTemporaryFile resDumpFile(ewsLogDir.path() + QStringLiteral("/ews_xmlresdump_XXXXXXX.xml"));
0372         resDumpFile.open();
0373         resDumpFile.setAutoRemove(false);
0374         resDumpFile.write(mResponseData.toUtf8());
0375         resDumpFile.close();
0376         qCDebug(EWSCLI_LOG) << "request  dumped to" << reqDumpFile.fileName();
0377         qCDebug(EWSCLI_LOG) << "response dumped to" << resDumpFile.fileName();
0378     } else {
0379         qCWarning(EWSCLI_LOG) << "failed to dump request and response";
0380     }
0381 }
0382 
0383 #include "moc_ewsrequest.cpp"