File indexing completed on 2025-02-16 04:50:06
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 "fakeewsconnection.h" 0008 0009 #include <QBuffer> 0010 #include <QRegularExpression> 0011 #include <QTcpSocket> 0012 #include <QXmlNamePool> 0013 #include <QXmlQuery> 0014 #include <QXmlResultItems> 0015 #include <QXmlSerializer> 0016 0017 #include "fakeewsserver_debug.h" 0018 0019 static const QHash<uint, QString> responseCodes = { 0020 {200, QStringLiteral("OK")}, 0021 {400, QStringLiteral("Bad Request")}, 0022 {401, QStringLiteral("Unauthorized")}, 0023 {403, QStringLiteral("Forbidden")}, 0024 {404, QStringLiteral("Not Found")}, 0025 {405, QStringLiteral("Method Not Allowed")}, 0026 {500, QStringLiteral("Internal Server Error")}, 0027 }; 0028 0029 static constexpr int streamingEventsHeartbeatIntervalSeconds = 5; 0030 0031 FakeEwsConnection::FakeEwsConnection(QTcpSocket *sock, FakeEwsServer *parent) 0032 : QObject(parent) 0033 , mSock(sock) 0034 , mContentLength(0) 0035 , mKeepAlive(false) 0036 , mState(Initial) 0037 , mAuthenticated(false) 0038 { 0039 qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Got new EWS connection."); 0040 connect(mSock.data(), &QTcpSocket::disconnected, this, &FakeEwsConnection::disconnected); 0041 connect(mSock.data(), &QTcpSocket::readyRead, this, &FakeEwsConnection::dataAvailable); 0042 connect(&mDataTimer, &QTimer::timeout, this, &FakeEwsConnection::dataTimeout); 0043 connect(&mStreamingRequestHeartbeat, &QTimer::timeout, this, &FakeEwsConnection::streamingRequestHeartbeat); 0044 connect(&mStreamingRequestTimeout, &QTimer::timeout, this, &FakeEwsConnection::streamingRequestTimeout); 0045 } 0046 0047 FakeEwsConnection::~FakeEwsConnection() 0048 { 0049 qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Connection closed."); 0050 } 0051 0052 void FakeEwsConnection::disconnected() 0053 { 0054 deleteLater(); 0055 } 0056 0057 void FakeEwsConnection::dataAvailable() 0058 { 0059 if (mState == Initial) { 0060 QByteArray line = mSock->readLine(); 0061 QList<QByteArray> tokens = line.split(' '); 0062 mKeepAlive = false; 0063 0064 if (tokens.size() < 3) { 0065 sendError(QStringLiteral("Invalid request header")); 0066 return; 0067 } 0068 if (tokens.at(0) != "POST") { 0069 sendError(QStringLiteral("Expected POST request")); 0070 return; 0071 } 0072 if (tokens.at(1) != "/EWS/Exchange.asmx") { 0073 sendError(QStringLiteral("Invalid EWS URL")); 0074 return; 0075 } 0076 mState = RequestReceived; 0077 } 0078 0079 if (mState == RequestReceived) { 0080 QByteArray line; 0081 do { 0082 line = mSock->readLine(); 0083 if (line.toLower().startsWith(QByteArray("content-length: "))) { 0084 bool ok; 0085 mContentLength = line.trimmed().mid(16).toUInt(&ok); 0086 if (!ok) { 0087 sendError(QStringLiteral("Failed to parse content length.")); 0088 return; 0089 } 0090 } else if (line.toLower().startsWith(QByteArray("authorization: basic "))) { 0091 if (line.trimmed().mid(21) == "dGVzdDp0ZXN0") { 0092 mAuthenticated = true; 0093 } 0094 } else if (line.toLower() == "connection: keep-alive\r\n") { 0095 mKeepAlive = true; 0096 } 0097 } while (!line.trimmed().isEmpty()); 0098 0099 if (line == "\r\n") { 0100 mState = HeadersReceived; 0101 } 0102 } 0103 0104 if (mState == HeadersReceived) { 0105 if (mContentLength == 0) { 0106 sendError(QStringLiteral("Expected content")); 0107 return; 0108 } 0109 0110 mContent += mSock->read(mContentLength - mContent.size()); 0111 0112 if (mContent.size() >= static_cast<int>(mContentLength)) { 0113 mDataTimer.stop(); 0114 0115 if (!mAuthenticated) { 0116 QString codeStr = responseCodes.value(401); 0117 QString response(QStringLiteral("HTTP/1.1 %1 %2\r\n" 0118 "WWW-Authenticate: Basic realm=\"Fake EWS Server\"\r\n" 0119 "Connection: close\r\n" 0120 "\r\n") 0121 .arg(401) 0122 .arg(codeStr)); 0123 response += codeStr; 0124 mSock->write(response.toLatin1()); 0125 mSock->disconnectFromHost(); 0126 return; 0127 } 0128 0129 FakeEwsServer::DialogEntry::HttpResponse resp = FakeEwsServer::EmptyResponse; 0130 0131 const auto server = qobject_cast<FakeEwsServer *>(parent()); 0132 const auto overrideReplyCallback = server->overrideReplyCallback(); 0133 if (overrideReplyCallback) { 0134 QXmlResultItems ri; 0135 QXmlNamePool namePool; 0136 resp = overrideReplyCallback(QString::fromUtf8(mContent), ri, namePool); 0137 } 0138 0139 if (resp == FakeEwsServer::EmptyResponse) { 0140 resp = parseRequest(QString::fromUtf8(mContent)); 0141 } 0142 bool chunked = false; 0143 0144 if (resp == FakeEwsServer::EmptyResponse) { 0145 resp = handleGetEventsRequest(QString::fromUtf8(mContent)); 0146 } 0147 0148 if (resp == FakeEwsServer::EmptyResponse) { 0149 resp = handleGetStreamingEventsRequest(QString::fromUtf8(mContent)); 0150 if (resp.second > 1000) { 0151 chunked = true; 0152 resp.second %= 1000; 0153 } 0154 } 0155 0156 auto defaultReplyCallback = server->defaultReplyCallback(); 0157 if (defaultReplyCallback && (resp == FakeEwsServer::EmptyResponse)) { 0158 QXmlResultItems ri; 0159 QXmlNamePool namePool; 0160 resp = defaultReplyCallback(QString::fromUtf8(mContent), ri, namePool); 0161 qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Returning response from default callback ") << resp.second << QStringLiteral(": ") << resp.first; 0162 } 0163 0164 if (resp == FakeEwsServer::EmptyResponse) { 0165 qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Returning default response 500."); 0166 resp = {QLatin1StringView(""), 500}; 0167 } 0168 0169 QByteArray buffer; 0170 QString codeStr = responseCodes.value(resp.second); 0171 QByteArray respContent = resp.first.toUtf8(); 0172 buffer += QStringLiteral("HTTP/1.1 %1 %2\r\n").arg(resp.second).arg(codeStr).toLatin1(); 0173 if (chunked) { 0174 buffer += "Transfer-Encoding: chunked\r\n"; 0175 buffer += "\r\n"; 0176 buffer += QByteArray::number(respContent.size(), 16) + "\r\n"; 0177 buffer += respContent + "\r\n"; 0178 } else { 0179 buffer += "Content-Length: " + QByteArray::number(respContent.size()) + "\r\n"; 0180 buffer += mKeepAlive ? "Connection: Keep-Alive\n" : "Connection: Close\r\n"; 0181 buffer += "\r\n"; 0182 buffer += respContent; 0183 } 0184 mSock->write(buffer); 0185 0186 if (!mKeepAlive && !chunked) { 0187 mSock->disconnectFromHost(); 0188 } 0189 mContent.clear(); 0190 mState = Initial; 0191 } else { 0192 mDataTimer.start(3000); 0193 } 0194 } 0195 } 0196 0197 void FakeEwsConnection::sendError(const QString &msg, ushort code) 0198 { 0199 qCWarningNC(EWSFAKE_LOG) << msg; 0200 QString codeStr = responseCodes.value(code); 0201 QByteArray response(QStringLiteral("HTTP/1.1 %1 %2\nConnection: close\n\n").arg(code).arg(codeStr).toLatin1()); 0202 response += msg.toLatin1(); 0203 mSock->write(response); 0204 mSock->disconnectFromHost(); 0205 } 0206 0207 void FakeEwsConnection::dataTimeout() 0208 { 0209 qCWarning(EWSFAKE_LOG) << QLatin1StringView("Timeout waiting for content."); 0210 sendError(QStringLiteral("Timeout waiting for content.")); 0211 } 0212 0213 FakeEwsServer::DialogEntry::HttpResponse FakeEwsConnection::parseRequest(const QString &content) 0214 { 0215 qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Got request: ") << content; 0216 0217 auto server = qobject_cast<FakeEwsServer *>(parent()); 0218 FakeEwsServer::DialogEntry::HttpResponse resp = FakeEwsServer::EmptyResponse; 0219 const auto dialogs{server->dialog()}; 0220 for (const FakeEwsServer::DialogEntry &de : dialogs) { 0221 QXmlResultItems ri; 0222 QByteArray resultBytes; 0223 QString result; 0224 QBuffer resultBuffer(&resultBytes); 0225 resultBuffer.open(QIODevice::WriteOnly); 0226 QXmlQuery query; 0227 QXmlSerializer xser(query, &resultBuffer); 0228 if (!de.xQuery.isNull()) { 0229 query.setFocus(content); 0230 query.setQuery(de.xQuery); 0231 query.evaluateTo(&xser); 0232 query.evaluateTo(&ri); 0233 if (ri.hasError()) { 0234 qCDebugNC(EWSFAKE_LOG) << QStringLiteral("XQuery failed due to errors - skipping"); 0235 continue; 0236 } 0237 result = QString::fromUtf8(resultBytes); 0238 } 0239 0240 if (!result.trimmed().isEmpty()) { 0241 qCDebugNC(EWSFAKE_LOG) << QStringLiteral("Got match for \"") << de.description << QStringLiteral("\""); 0242 if (de.replyCallback) { 0243 resp = de.replyCallback(content, ri, query.namePool()); 0244 qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Returning response from callback ") << resp.second << QStringLiteral(": ") << resp.first; 0245 } else { 0246 resp = {result.trimmed(), 200}; 0247 qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Returning response from XQuery ") << resp.second << QStringLiteral(": ") << resp.first; 0248 } 0249 break; 0250 } 0251 } 0252 0253 if (resp == FakeEwsServer::EmptyResponse) { 0254 qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Returning empty response."); 0255 qCInfoNC(EWSFAKE_LOG) << content; 0256 } 0257 0258 return resp; 0259 } 0260 0261 FakeEwsServer::DialogEntry::HttpResponse FakeEwsConnection::handleGetEventsRequest(const QString &content) 0262 { 0263 const QRegularExpression re(QStringLiteral( 0264 "<?xml .*<\\w*:?GetEvents[ " 0265 ">].*<\\w*:?SubscriptionId>(?<subid>[^<]*)</\\w*:?SubscriptionId><\\w*:?Watermark>(?<watermark>[^<]*)</\\w*:?Watermark></\\w*:?GetEvents>.*")); 0266 0267 QRegularExpressionMatch match = re.match(content); 0268 if (!match.hasMatch() || match.hasPartialMatch()) { 0269 qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Not a valid GetEvents request."); 0270 return FakeEwsServer::EmptyResponse; 0271 } 0272 0273 qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Got valid GetEvents request."); 0274 0275 QString resp = QStringLiteral( 0276 "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" 0277 "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" " 0278 "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " 0279 "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" " 0280 "xmlns:m=\"http://schemas.microsoft.com/exchange/services/2006/messages\" " 0281 "xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">" 0282 "<soap:Header>" 0283 "<t:ServerVersionInfo MajorVersion=\"8\" MinorVersion=\"0\" MajorBuildNumber=\"628\" MinorBuildNumber=\"0\" />" 0284 "</soap:Header>" 0285 "<soap:Body>" 0286 "<m:GetEventsResponse xmlns=\"http://schemas.microsoft.com/exchange/services/2006/types\">" 0287 "<m:ResponseMessages>" 0288 "<m:GetEventsResponseMessage ResponseClass=\"Success\">" 0289 "<m:ResponseCode>NoError</m:ResponseCode>" 0290 "<m:Notification>"); 0291 0292 if (match.captured(QStringLiteral("subid")).isEmpty() || match.captured(QStringLiteral("watermark")).isEmpty()) { 0293 qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Missing subscription id or watermark."); 0294 const QString errorResp = QStringLiteral( 0295 "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" 0296 "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" " 0297 "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " 0298 "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" " 0299 "xmlns:m=\"http://schemas.microsoft.com/exchange/services/2006/messages\" " 0300 "xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">" 0301 "<soap:Header>" 0302 "<t:ServerVersionInfo MajorVersion=\"8\" MinorVersion=\"0\" MajorBuildNumber=\"628\" MinorBuildNumber=\"0\" />" 0303 "</soap:Header>" 0304 "<soap:Body>" 0305 "<m:GetEventsResponse>" 0306 "<m:ResponseMessages>" 0307 "<m:GetEventsResponseMessage ResponseClass=\"Error\">" 0308 "<m:MessageText>Missing subscription id or watermark.</m:MessageText>" 0309 "<m:ResponseCode>ErrorInvalidPullSubscriptionId</m:ResponseCode>" 0310 "<m:DescriptiveLinkKey>0</m:DescriptiveLinkKey>" 0311 "</m:GetEventsResponseMessage>" 0312 "</m:ResponseMessages>" 0313 "</m:GetEventsResponse>" 0314 "</soap:Body>" 0315 "</soap:Envelope>"); 0316 return {errorResp, 200}; 0317 } 0318 0319 resp += QLatin1StringView("<SubscriptionId>") + match.captured(QStringLiteral("subid")) + QLatin1StringView("<SubscriptionId>"); 0320 resp += QLatin1StringView("<PreviousWatermark>") + match.captured(QStringLiteral("watermark")) + QLatin1StringView("<PreviousWatermark>"); 0321 resp += QStringLiteral("<MoreEvents>false<MoreEvents>"); 0322 0323 auto server = qobject_cast<FakeEwsServer *>(parent()); 0324 const QStringList events = server->retrieveEventsXml(); 0325 qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Returning %1 events.").arg(events.size()); 0326 for (const QString &eventXml : events) { 0327 resp += eventXml; 0328 } 0329 0330 resp += QStringLiteral( 0331 "</m:Notification></m:GetEventsResponseMessage></m:ResponseMessages>" 0332 "</m:GetEventsResponse></soap:Body></soap:Envelope>"); 0333 0334 return {resp, 200}; 0335 } 0336 0337 QString FakeEwsConnection::prepareEventsResponse(const QStringList &events) 0338 { 0339 QString resp = QStringLiteral( 0340 "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" 0341 "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" " 0342 "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " 0343 "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" " 0344 "xmlns:m=\"http://schemas.microsoft.com/exchange/services/2006/messages\" " 0345 "xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">" 0346 "<soap:Header>" 0347 "<t:ServerVersionInfo MajorVersion=\"8\" MinorVersion=\"0\" MajorBuildNumber=\"628\" MinorBuildNumber=\"0\" />" 0348 "</soap:Header>" 0349 "<soap:Body>" 0350 "<m:GetStreamingEventsResponse>" 0351 "<m:ResponseMessages>" 0352 "<m:GetStreamingEventsResponseMessage ResponseClass=\"Success\">" 0353 "<m:ResponseCode>NoError</m:ResponseCode>" 0354 "<m:ConnectionStatus>OK</m:ConnectionStatus>"); 0355 0356 if (!events.isEmpty()) { 0357 resp += QLatin1StringView("<m:Notifications><m:Notification><SubscriptionId>") + mStreamingSubId + QLatin1StringView("<SubscriptionId>"); 0358 0359 qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Returning %1 events.").arg(events.size()); 0360 for (const QString &eventXml : std::as_const(events)) { 0361 resp += eventXml; 0362 } 0363 0364 resp += QStringLiteral("</m:Notification></m:Notifications>"); 0365 } 0366 resp += QStringLiteral( 0367 "</m:GetStreamingEventsResponseMessage></m:ResponseMessages>" 0368 "</m:GetStreamingEventsResponse></soap:Body></soap:Envelope>"); 0369 0370 return resp; 0371 } 0372 0373 FakeEwsServer::DialogEntry::HttpResponse FakeEwsConnection::handleGetStreamingEventsRequest(const QString &content) 0374 { 0375 const QRegularExpression re( 0376 QStringLiteral("<?xml .*<\\w*:?GetStreamingEvents[ " 0377 ">].*<\\w*:?SubscriptionIds><\\w*:?SubscriptionId>(?<subid>[^<]*)</\\w*:?SubscriptionId></" 0378 "\\w*:?SubscriptionIds>.*<\\w*:?ConnectionTimeout>(?<timeout>[^<]*)</\\w*:?ConnectionTimeout></\\w*:?GetStreamingEvents>.*")); 0379 0380 QRegularExpressionMatch match = re.match(content); 0381 if (!match.hasMatch() || match.hasPartialMatch()) { 0382 qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Not a valid GetStreamingEvents request."); 0383 return FakeEwsServer::EmptyResponse; 0384 } 0385 0386 qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Got valid GetStreamingEvents request."); 0387 0388 if (match.captured(QStringLiteral("subid")).isEmpty() || match.captured(QStringLiteral("timeout")).isEmpty()) { 0389 qCInfoNC(EWSFAKE_LOG) << QStringLiteral("Missing subscription id or timeout."); 0390 const QString errorResp = QStringLiteral( 0391 "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" 0392 "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" " 0393 "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " 0394 "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" " 0395 "xmlns:m=\"http://schemas.microsoft.com/exchange/services/2006/messages\" " 0396 "xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">" 0397 "<soap:Header>" 0398 "<t:ServerVersionInfo MajorVersion=\"8\" MinorVersion=\"0\" MajorBuildNumber=\"628\" MinorBuildNumber=\"0\" />" 0399 "</soap:Header>" 0400 "<soap:Body>" 0401 "<m:GetStreamingEventsResponse>" 0402 "<m:ResponseMessages>" 0403 "<m:GetStreamingEventsResponseMessage ResponseClass=\"Error\">" 0404 "<m:MessageText>Missing subscription id or timeout.</m:MessageText>" 0405 "<m:ResponseCode>ErrorInvalidSubscription</m:ResponseCode>" 0406 "<m:DescriptiveLinkKey>0</m:DescriptiveLinkKey>" 0407 "</m:GetEventsResponseMessage>" 0408 "</m:ResponseMessages>" 0409 "</m:GetEventsResponse>" 0410 "</soap:Body>" 0411 "</soap:Envelope>"); 0412 return {errorResp, 200}; 0413 } 0414 0415 mStreamingSubId = match.captured(QStringLiteral("subid")); 0416 0417 auto server = qobject_cast<FakeEwsServer *>(parent()); 0418 const QStringList events = server->retrieveEventsXml(); 0419 0420 QString resp = prepareEventsResponse(events); 0421 0422 mStreamingRequestTimeout.start(match.captured(QStringLiteral("timeout")).toInt() * 1000 * 60); 0423 mStreamingRequestHeartbeat.setSingleShot(false); 0424 mStreamingRequestHeartbeat.start(streamingEventsHeartbeatIntervalSeconds * 1000); 0425 0426 Q_EMIT streamingRequestStarted(this); 0427 0428 return {resp, 1200}; 0429 } 0430 0431 void FakeEwsConnection::streamingRequestHeartbeat() 0432 { 0433 sendEvents(QStringList()); 0434 } 0435 0436 void FakeEwsConnection::streamingRequestTimeout() 0437 { 0438 mStreamingRequestTimeout.stop(); 0439 mStreamingRequestHeartbeat.stop(); 0440 mSock->write("0\r\n\r\n"); 0441 mSock->disconnectFromHost(); 0442 } 0443 0444 void FakeEwsConnection::sendEvents(const QStringList &events) 0445 { 0446 QByteArray resp = prepareEventsResponse(events).toUtf8(); 0447 0448 mSock->write(QByteArray::number(resp.size(), 16) + "\r\n" + resp + "\r\n"); 0449 } 0450 0451 #include "moc_fakeewsconnection.cpp"