diff --git a/src/libsync/bulkpropagatorjob.cpp b/src/libsync/bulkpropagatorjob.cpp index 0f0a7db3fcfa4..58e1ab82f0c95 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 b71f7261eac06..39b42f7518589 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 f74727ec7bb80..78b04205cc361 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 8be463ecd21c9..f751c58253fbe 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 33cd8892d7335..a32e796a597de 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 399546dcd0459..c0d880be6097f 100644 --- a/src/libsync/syncfileitem.h +++ b/src/libsync/syncfileitem.h @@ -287,6 +287,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)