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"