From 6a0eb553199d3930d9dce2ec1e37ed51504be634 Mon Sep 17 00:00:00 2001 From: alex-z Date: Thu, 30 Nov 2023 15:00:18 +0100 Subject: [PATCH] Report error Virus Detected. Updated tests. Signed-off-by: alex-z --- src/libsync/bulkpropagatorjob.cpp | 3 ++ src/libsync/owncloudpropagator.cpp | 10 ++++++- src/libsync/owncloudpropagator_p.h | 47 ++++++++++++++++++++++++++++++ src/libsync/propagateupload.cpp | 3 ++ src/libsync/propagateuploadng.cpp | 6 ++++ src/libsync/propagateuploadv1.cpp | 3 ++ src/libsync/syncfileitem.h | 2 ++ test/testnextcloudpropagator.cpp | 23 +++++++++++++++ 8 files changed, 96 insertions(+), 1 deletion(-) diff --git a/src/libsync/bulkpropagatorjob.cpp b/src/libsync/bulkpropagatorjob.cpp index 08b3311f18eb1..b666f55d5d600 100644 --- a/src/libsync/bulkpropagatorjob.cpp +++ b/src/libsync/bulkpropagatorjob.cpp @@ -366,6 +366,9 @@ void BulkPropagatorJob::slotPutFinishedOneFile(const BulkUploadItem &singleFile, singleFile._item->_requestId = job->requestId(); if (singleFile._item->_httpErrorCode != 200) { commonErrorHandling(singleFile._item, fileReply[QStringLiteral("message")].toString()); + const auto exceptionParsed = getExceptionFromReply(job->reply()); + singleFile._item->_errorExceptionName = exceptionParsed.first; + singleFile._item->_errorExceptionMessage = exceptionParsed.second; return; } diff --git a/src/libsync/owncloudpropagator.cpp b/src/libsync/owncloudpropagator.cpp index 8741de22c692d..e0fdd97736c66 100644 --- a/src/libsync/owncloudpropagator.cpp +++ b/src/libsync/owncloudpropagator.cpp @@ -358,7 +358,15 @@ void PropagateItemJob::reportClientStatuses() propagator()->account()->reportClientStatus(ClientStatusReporting::Status::DownloadError_ConflictInvalidCharacters); } else if (_item->_httpErrorCode != 0 && _item->_httpErrorCode != 200 && _item->_httpErrorCode != 201 && _item->_httpErrorCode != 204) { if (_item->_direction == SyncFileItem::Up) { - propagator()->account()->reportClientStatus(ClientStatusReporting::Status::UploadError_ServerError); + const auto isCodeBadReqOrUnsupportedMediaType = (_item->_httpErrorCode == 400 || _item->_httpErrorCode == 415); + const auto isExceptionInfoPresent = !_item->_errorExceptionName.isEmpty() && !_item->_errorExceptionMessage.isEmpty(); + if (isCodeBadReqOrUnsupportedMediaType && isExceptionInfoPresent + && _item->_errorExceptionName.contains(QStringLiteral("UnsupportedMediaType")) + && _item->_errorExceptionMessage.contains(QStringLiteral("virus"), Qt::CaseInsensitive)) { + propagator()->account()->reportClientStatus(ClientStatusReporting::Status::UploadError_Virus_Detected); + } else { + propagator()->account()->reportClientStatus(ClientStatusReporting::Status::UploadError_ServerError); + } } else { propagator()->account()->reportClientStatus(ClientStatusReporting::Status::DownloadError_ServerError); } diff --git a/src/libsync/owncloudpropagator_p.h b/src/libsync/owncloudpropagator_p.h index 6337b2f3cfb9c..7d0c121bb4af6 100644 --- a/src/libsync/owncloudpropagator_p.h +++ b/src/libsync/owncloudpropagator_p.h @@ -48,6 +48,7 @@ inline bool fileIsStillChanging(const OCC::SyncFileItem &item) namespace OCC { + inline QByteArray getEtagFromReply(QNetworkReply *reply) { QByteArray ocEtag = parseEtag(reply->rawHeader("OC-ETag")); @@ -62,6 +63,52 @@ inline QByteArray getEtagFromReply(QNetworkReply *reply) return ret; } +inline QPair getExceptionFromReply(QNetworkReply *reply) +{ + Q_ASSERT(reply); + if (!reply) { + qDebug() << "Could not parse null reply"; + return {}; + } + const auto httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + // only for BadRequest and UnsupportedMediaType + if (httpStatusCode != 400 && httpStatusCode != 415) { + return {}; + } + + // we must not modify the buffer + const auto replyBody = reply->peek(reply->bytesAvailable()); + + // parse exception name + auto exceptionNameStartIndex = replyBody.indexOf(QByteArrayLiteral("")); + if (exceptionNameStartIndex == -1) { + qDebug() << "Could not parse exception. No tag."; + return {}; + } + exceptionNameStartIndex += QByteArrayLiteral("").size(); + const auto exceptionNameEndIndex = replyBody.indexOf('<', exceptionNameStartIndex); + if (exceptionNameEndIndex == -1) { + return {}; + } + const auto exceptionName = replyBody.mid(exceptionNameStartIndex, exceptionNameEndIndex - exceptionNameStartIndex); + + // parse exception message + auto exceptionMessageStartIndex = replyBody.indexOf(QByteArrayLiteral(""), exceptionNameEndIndex); + if (exceptionMessageStartIndex == -1) { + qDebug() << "Could not parse exception. No tag."; + return {exceptionName, {}}; + } + exceptionMessageStartIndex += QByteArrayLiteral("").size(); + const auto exceptionMessageEndIndex = replyBody.indexOf('<', exceptionMessageStartIndex); + if (exceptionMessageEndIndex == -1) { + return {exceptionName, {}}; + } + + const auto exceptionMessage = replyBody.mid(exceptionMessageStartIndex, exceptionMessageEndIndex - exceptionMessageStartIndex); + + return {exceptionName, exceptionMessage}; +} + /** * Given an error from the network, map to a SyncFileItem::Status error */ diff --git a/src/libsync/propagateupload.cpp b/src/libsync/propagateupload.cpp index ca08042eeaf20..d0d03a56c71e5 100644 --- a/src/libsync/propagateupload.cpp +++ b/src/libsync/propagateupload.cpp @@ -112,6 +112,9 @@ bool PollJob::finished() _item->_requestId = requestId(); _item->_status = classifyError(err, _item->_httpErrorCode); _item->_errorString = errorString(); + const auto exceptionParsed = getExceptionFromReply(reply()); + _item->_errorExceptionName = exceptionParsed.first; + _item->_errorExceptionMessage = exceptionParsed.second; if (_item->_status == SyncFileItem::FatalError || _item->_httpErrorCode >= 400) { if (_item->_status != SyncFileItem::FatalError diff --git a/src/libsync/propagateuploadng.cpp b/src/libsync/propagateuploadng.cpp index 4642d857b6929..7530bc01727de 100644 --- a/src/libsync/propagateuploadng.cpp +++ b/src/libsync/propagateuploadng.cpp @@ -411,6 +411,9 @@ void PropagateUploadFileNG::slotPutFinished() _item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); _item->_requestId = job->requestId(); commonErrorHandling(job); + const auto exceptionParsed = getExceptionFromReply(job->reply()); + _item->_errorExceptionName = exceptionParsed.first; + _item->_errorExceptionMessage = exceptionParsed.second; return; } @@ -496,6 +499,9 @@ void PropagateUploadFileNG::slotMoveJobFinished() if (err != QNetworkReply::NoError) { commonErrorHandling(job); + const auto exceptionParsed = getExceptionFromReply(job->reply()); + _item->_errorExceptionName = exceptionParsed.first; + _item->_errorExceptionMessage = exceptionParsed.second; return; } diff --git a/src/libsync/propagateuploadv1.cpp b/src/libsync/propagateuploadv1.cpp index c51fdd3bee7a3..0e11151bd99e9 100644 --- a/src/libsync/propagateuploadv1.cpp +++ b/src/libsync/propagateuploadv1.cpp @@ -218,6 +218,9 @@ void PropagateUploadFileV1::slotPutFinished() QNetworkReply::NetworkError err = job->reply()->error(); if (err != QNetworkReply::NoError) { commonErrorHandling(job); + const auto exceptionParsed = getExceptionFromReply(job->reply()); + _item->_errorExceptionName = exceptionParsed.first; + _item->_errorExceptionMessage = exceptionParsed.second; return; } diff --git a/src/libsync/syncfileitem.h b/src/libsync/syncfileitem.h index eb263f3424bfc..f2fe09a76ca34 100644 --- a/src/libsync/syncfileitem.h +++ b/src/libsync/syncfileitem.h @@ -285,6 +285,8 @@ class OWNCLOUDSYNC_EXPORT SyncFileItem quint16 _httpErrorCode = 0; RemotePermissions _remotePerm; QString _errorString; // Contains a string only in case of error + QString _errorExceptionName; // Contains a server exception string only in case of error + QString _errorExceptionMessage; // Contains a server exception message string only in case of error QByteArray _responseTimeStamp; QByteArray _requestId; // X-Request-Id of the failed request quint32 _affectedItems = 1; // the number of affected items by the operation on this item. diff --git a/test/testnextcloudpropagator.cpp b/test/testnextcloudpropagator.cpp index 954c665f5f91c..812c5b43c7008 100644 --- a/test/testnextcloudpropagator.cpp +++ b/test/testnextcloudpropagator.cpp @@ -9,6 +9,7 @@ #include "propagatedownload.h" #include "owncloudpropagator_p.h" +#include "syncenginetestutils.h" using namespace OCC; namespace OCC { @@ -76,6 +77,28 @@ private slots: QCOMPARE(parseEtag(test.first), QByteArray(test.second)); } } + + void testParseException() + { + QNetworkRequest request; + request.setUrl(QStringLiteral("http://cloud.example.de/")); + const auto body = QByteArrayLiteral( + "\n" + "\n" + "Sabre\\Exception\\UnsupportedMediaType\n" + "Virus detected!\n" + ""); + const auto reply = new FakeErrorReply(QNetworkAccessManager::PutOperation, + request, + this, + 415, body); + const auto exceptionParsed = OCC::getExceptionFromReply(reply); + // verify parsing succeeded + QVERIFY(!exceptionParsed.first.isEmpty()); + QVERIFY(!exceptionParsed.second.isEmpty()); + // verify buffer is not changed + QCOMPARE(reply->readAll().size(), body.size()); + } }; QTEST_APPLESS_MAIN(TestNextcloudPropagator)