diff options
author | alex-z <blackslayer4@gmail.com> | 2021-12-20 15:59:08 +0300 |
---|---|---|
committer | allexzander (Rebase PR Action) <allexzander@users.noreply.github.com> | 2022-01-11 16:37:09 +0300 |
commit | 190d278fd4302cbb963338b4e224b326834ef0e2 (patch) | |
tree | effaa94fc8d61c58d8b0364a058fe87bfac93ab1 | |
parent | 5b0e2d8ed071d6fb7d0ff9152c089df093740bee (diff) |
Checksum validation PropagateDownload unit tests.
Signed-off-by: alex-z <blackslayer4@gmail.com>
-rw-r--r-- | src/common/checksums.cpp | 8 | ||||
-rw-r--r-- | src/common/checksums.h | 5 | ||||
-rw-r--r-- | src/libsync/account.cpp | 5 | ||||
-rw-r--r-- | src/libsync/account.h | 2 | ||||
-rw-r--r-- | src/libsync/networkjobs.cpp | 6 | ||||
-rw-r--r-- | src/libsync/networkjobs.h | 4 | ||||
-rw-r--r-- | src/libsync/propagatedownload.cpp | 25 | ||||
-rw-r--r-- | src/libsync/propagatedownload.h | 3 | ||||
-rw-r--r-- | test/testchecksumvalidator.cpp | 5 | ||||
-rw-r--r-- | test/testsyncengine.cpp | 50 |
10 files changed, 86 insertions, 27 deletions
diff --git a/src/common/checksums.cpp b/src/common/checksums.cpp index 5cef55bfb..a298e5d2f 100644 --- a/src/common/checksums.cpp +++ b/src/common/checksums.cpp @@ -337,7 +337,7 @@ ComputeChecksum *ValidateChecksumHeader::prepareStart(const QByteArray &checksum if (!parseChecksumHeader(checksumHeader, &_expectedChecksumType, &_expectedChecksum)) { qCWarning(lcChecksums) << "Checksum header malformed:" << checksumHeader; - emit validationFailed(tr("The checksum header is malformed."), ChecksumHeaderMalformed); + emit validationFailed(tr("The checksum header is malformed."), _calculatedChecksumType, _calculatedChecksum, ChecksumHeaderMalformed); return nullptr; } @@ -377,11 +377,13 @@ void ValidateChecksumHeader::slotChecksumCalculated(const QByteArray &checksumTy _calculatedChecksum = checksum; if (checksumType != _expectedChecksumType) { - emit validationFailed(tr("The checksum header contained an unknown checksum type \"%1\"").arg(QString::fromLatin1(_expectedChecksumType)), ChecksumTypeUnknown); + emit validationFailed(tr("The checksum header contained an unknown checksum type \"%1\"").arg(QString::fromLatin1(_expectedChecksumType)), + _calculatedChecksumType, _calculatedChecksum, ChecksumTypeUnknown); return; } if (checksum != _expectedChecksum) { - emit validationFailed(tr(R"(The downloaded file does not match the checksum, it will be resumed. "%1" != "%2")").arg(QString::fromUtf8(_expectedChecksum), QString::fromUtf8(checksum)), ChecksumMismatch); + emit validationFailed(tr(R"(The downloaded file does not match the checksum, it will be resumed. "%1" != "%2")").arg(QString::fromUtf8(_expectedChecksum), QString::fromUtf8(checksum)), + _calculatedChecksumType, _calculatedChecksum, ChecksumMismatch); return; } emit validated(checksumType, checksum); diff --git a/src/common/checksums.h b/src/common/checksums.h index 80716040c..8ade282c7 100644 --- a/src/common/checksums.h +++ b/src/common/checksums.h @@ -146,6 +146,8 @@ public: ChecksumTypeUnknown, ChecksumMismatch, }; + Q_ENUM(FailureReason) + explicit ValidateChecksumHeader(QObject *parent = nullptr); /** @@ -172,7 +174,8 @@ public: signals: void validated(const QByteArray &checksumType, const QByteArray &checksum); - void validationFailed(const QString &errMsg, FailureReason reason); + void validationFailed(const QString &errMsg, const QByteArray &calculatedChecksumType, + const QByteArray &calculatedChecksum, ValidateChecksumHeader::FailureReason reason); private slots: void slotChecksumCalculated(const QByteArray &checksumType, const QByteArray &checksum); diff --git a/src/libsync/account.cpp b/src/libsync/account.cpp index 16d9bf4b2..35aa36ce4 100644 --- a/src/libsync/account.cpp +++ b/src/libsync/account.cpp @@ -641,6 +641,11 @@ bool Account::isChecksumRecalculateRequestSupported() const return serverVersionInt() >= makeServerVersion(checksumRecalculateRequestServerVersionMinSupportedMajor, 0, 0); } +int Account::checksumRecalculateServerVersionMinSupportedMajor() const +{ + return checksumRecalculateRequestServerVersionMinSupportedMajor; +} + void Account::setServerVersion(const QString &version) { if (version == _serverVersion) { diff --git a/src/libsync/account.h b/src/libsync/account.h index b1044f4d4..a80eff079 100644 --- a/src/libsync/account.h +++ b/src/libsync/account.h @@ -234,6 +234,8 @@ public: bool isChecksumRecalculateRequestSupported() const; + int checksumRecalculateServerVersionMinSupportedMajor() const; + /** True when the server connection is using HTTP2 */ bool isHttp2Supported() { return _http2Supported; } void setHttp2Supported(bool value) { _http2Supported = value; } diff --git a/src/libsync/networkjobs.cpp b/src/libsync/networkjobs.cpp index 5e29ab8c4..c515b9a37 100644 --- a/src/libsync/networkjobs.cpp +++ b/src/libsync/networkjobs.cpp @@ -1084,12 +1084,12 @@ bool SimpleNetworkJob::finished() return true; } -SimpleFileManipulationNetworkJob::SimpleFileManipulationNetworkJob(AccountPtr account, const QString &filePath, QObject *parent) +SimpleFileJob::SimpleFileJob(AccountPtr account, const QString &filePath, QObject *parent) : AbstractNetworkJob(account, filePath, parent) { } -QNetworkReply *SimpleFileManipulationNetworkJob::startRequest( +QNetworkReply *SimpleFileJob::startRequest( const QByteArray &verb, QNetworkRequest req, QIODevice *requestBody) { const auto davUrlString = makeDavUrl(path()).toString(); @@ -1098,7 +1098,7 @@ QNetworkReply *SimpleFileManipulationNetworkJob::startRequest( return reply; } -bool SimpleFileManipulationNetworkJob::finished() +bool SimpleFileJob::finished() { emit finishedSignal(reply()); return true; diff --git a/src/libsync/networkjobs.h b/src/libsync/networkjobs.h index 037379521..d85464db7 100644 --- a/src/libsync/networkjobs.h +++ b/src/libsync/networkjobs.h @@ -502,11 +502,11 @@ private slots: * @brief A basic file manipulation job * @ingroup libsync */ -class OWNCLOUDSYNC_EXPORT SimpleFileManipulationNetworkJob : public AbstractNetworkJob +class OWNCLOUDSYNC_EXPORT SimpleFileJob : public AbstractNetworkJob { Q_OBJECT public: - explicit SimpleFileManipulationNetworkJob(AccountPtr account, const QString &filePath, QObject *parent = nullptr); + explicit SimpleFileJob(AccountPtr account, const QString &filePath, QObject *parent = nullptr); QNetworkReply *startRequest(const QByteArray &verb, QNetworkRequest req = QNetworkRequest(), QIODevice *requestBody = nullptr); diff --git a/src/libsync/propagatedownload.cpp b/src/libsync/propagatedownload.cpp index b3790916b..35337669c 100644 --- a/src/libsync/propagatedownload.cpp +++ b/src/libsync/propagatedownload.cpp @@ -922,7 +922,8 @@ void PropagateDownloadFile::slotGetFinished() validator->start(_tmpFile.fileName(), checksumHeader); } -void PropagateDownloadFile::slotChecksumFail(const QString &errMsg, ValidateChecksumHeader::FailureReason reason) +void PropagateDownloadFile::slotChecksumFail(const QString &errMsg, + const QByteArray &calculatedChecksumType, const QByteArray &calculatedChecksum, ValidateChecksumHeader::FailureReason reason) { const auto processChecksumFailure = [this, errMsg]() { FileSystem::remove(_tmpFile.fileName()); @@ -932,24 +933,23 @@ void PropagateDownloadFile::slotChecksumFail(const QString &errMsg, ValidateChec if (reason == ValidateChecksumHeader::FailureReason::ChecksumMismatch && propagator()->account()->isChecksumRecalculateRequestSupported()) { - if (const auto validator = qobject_cast<ValidateChecksumHeader *>(sender())) { - const QByteArray calculatedChecksum(validator->calculatedChecksumType() + ':' + validator->calculatedChecksum()); + const QByteArray calculatedChecksumHeader(calculatedChecksumType + ':' + calculatedChecksum); const QString fullRemotePathForFile(propagator()->fullRemotePath(_isEncrypted ? _item->_encryptedFileName : _item->_file)); - auto *job = new SimpleFileManipulationNetworkJob(propagator()->account(), fullRemotePathForFile); - QObject::connect(job, &SimpleFileManipulationNetworkJob::finishedSignal, this, [this, calculatedChecksum, processChecksumFailure](QNetworkReply *reply) { + auto *job = new SimpleFileJob(propagator()->account(), fullRemotePathForFile); + QObject::connect(job, &SimpleFileJob::finishedSignal, this, [this, calculatedChecksumHeader, processChecksumFailure](QNetworkReply *reply) { if (reply->error() == QNetworkReply::NoError) { - const auto newChecksumFromServer = reply->rawHeader(checkSumHeaderC); - if (newChecksumFromServer == calculatedChecksum) { - const auto newChecksumFromServerSplit = newChecksumFromServer.split(':'); - if (newChecksumFromServerSplit.size() > 1) { + const auto newChecksumHeaderFromServer = reply->rawHeader(checkSumHeaderC); + if (newChecksumHeaderFromServer == calculatedChecksumHeader) { + const auto newChecksumHeaderFromServerSplit = newChecksumHeaderFromServer.split(':'); + if (newChecksumHeaderFromServerSplit.size() > 1) { transmissionChecksumValidated( - newChecksumFromServerSplit.first(), newChecksumFromServerSplit.last()); + newChecksumHeaderFromServerSplit.first(), newChecksumHeaderFromServerSplit.last()); return; } } qCCritical(lcPropagateDownload) << "Checksum recalculation has failed for file:" << reply->url() - << " " << checkSumHeaderC << " received is:" << newChecksumFromServer; + << " " << checkSumHeaderC << " received is:" << newChecksumHeaderFromServer; } if (reply->error() != QNetworkReply::NoError) { @@ -964,10 +964,9 @@ void PropagateDownloadFile::slotChecksumFail(const QString &errMsg, ValidateChec qCWarning(lcPropagateDownload) << "Checksum validation has failed for file:" << fullRemotePathForFile << " Requesting checksum recalculation on the server..."; QNetworkRequest req; - req.setRawHeader(checksumRecalculateOnServer, validator->calculatedChecksumType()); + req.setRawHeader(checksumRecalculateOnServer, calculatedChecksumType); job->startRequest(QByteArrayLiteral("PATCH"), req); return; - } } processChecksumFailure(); diff --git a/src/libsync/propagatedownload.h b/src/libsync/propagatedownload.h index ce13f879c..28a128a2d 100644 --- a/src/libsync/propagatedownload.h +++ b/src/libsync/propagatedownload.h @@ -236,7 +236,8 @@ private slots: void abort(PropagatorJob::AbortType abortType) override; void slotDownloadProgress(qint64, qint64); - void slotChecksumFail(const QString &errMsg, ValidateChecksumHeader::FailureReason reason); + void slotChecksumFail(const QString &errMsg, const QByteArray &calculatedChecksumType, + const QByteArray &calculatedChecksum, ValidateChecksumHeader::FailureReason reason); private: void startAfterIsEncryptedIsChecked(); diff --git a/test/testchecksumvalidator.cpp b/test/testchecksumvalidator.cpp index f797e0eb2..addca54bb 100644 --- a/test/testchecksumvalidator.cpp +++ b/test/testchecksumvalidator.cpp @@ -43,8 +43,11 @@ using namespace OCC::Utility; _successDown = true; } - void slotDownError(const QString &errMsg, ValidateChecksumHeader::FailureReason reason) + void slotDownError(const QString &errMsg, const QByteArray &calculatedChecksumType, + const QByteArray &calculatedChecksum, ValidateChecksumHeader::FailureReason reason) { + Q_UNUSED(calculatedChecksumType); + Q_UNUSED(calculatedChecksum); QCOMPARE(_expectedError, errMsg); QCOMPARE(_expectedFailureReason, reason); _errorSeen = true; diff --git a/test/testsyncengine.cpp b/test/testsyncengine.cpp index 328e813ee..856cf560e 100644 --- a/test/testsyncengine.cpp +++ b/test/testsyncengine.cpp @@ -8,6 +8,7 @@ #include <QtTest> #include "syncenginetestutils.h" #include <syncengine.h> +#include <propagatorjobs.h> using namespace OCC; @@ -551,16 +552,27 @@ private slots: QObject parent; QByteArray checksumValue; + QByteArray checksumValueRecalculated; QByteArray contentMd5Value; + bool isChecksumRecalculateSupported = false; fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * { if (op == QNetworkAccessManager::GetOperation) { auto reply = new FakeGetReply(fakeFolder.remoteModifier(), op, request, &parent); if (!checksumValue.isNull()) - reply->setRawHeader("OC-Checksum", checksumValue); + reply->setRawHeader(OCC::checkSumHeaderC, checksumValue); if (!contentMd5Value.isNull()) - reply->setRawHeader("Content-MD5", contentMd5Value); + reply->setRawHeader(OCC::contentMd5HeaderC, contentMd5Value); return reply; + } else if (op == QNetworkAccessManager::CustomOperation) { + if (request.hasRawHeader(OCC::checksumRecalculateOnServer)) { + if (!isChecksumRecalculateSupported) { + return new FakeErrorReply(op, request, &parent, 402); + } + auto reply = new FakeGetReply(fakeFolder.remoteModifier(), op, request, &parent); + reply->setRawHeader(OCC::checkSumHeaderC, checksumValueRecalculated); + return reply; + } } return nullptr; }); @@ -575,8 +587,11 @@ private slots: fakeFolder.remoteModifier().create("A/a4", 16, 'A'); QVERIFY(!fakeFolder.syncOnce()); + const QByteArray matchedSha1Checksum(QByteArrayLiteral("SHA1:19b1928d58a2030d08023f3d7054516dbc186f20")); + const QByteArray mismatchedSha1Checksum(matchedSha1Checksum.chopped(1)); + // Good OC-Checksum - checksumValue = "SHA1:19b1928d58a2030d08023f3d7054516dbc186f20"; // printf 'A%.0s' {1..16} | sha1sum - + checksumValue = matchedSha1Checksum; // printf 'A%.0s' {1..16} | sha1sum - QVERIFY(fakeFolder.syncOnce()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); checksumValue = QByteArray(); @@ -610,6 +625,35 @@ private slots: checksumValue = "Unsupported:XXXX SHA1:19b1928d58a2030d08023f3d7054516dbc186f20 Invalid:XxX"; QVERIFY(fakeFolder.syncOnce()); // The supported SHA1 checksum is valid now, so the file are downloaded QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + // Begin Test mismatch recalculation--------------------------------------------------------------------------------- + + const auto prevServerVersion = fakeFolder.account()->serverVersion(); + fakeFolder.account()->setServerVersion(QString("%1.0.0").arg(fakeFolder.account()->checksumRecalculateServerVersionMinSupportedMajor())); + + // Mismatched OC-Checksum and X-Recalculate-Hash is not supported -> sync must fail + isChecksumRecalculateSupported = false; + checksumValue = mismatchedSha1Checksum; + checksumValueRecalculated = matchedSha1Checksum; + fakeFolder.remoteModifier().create("A/a9", 16, 'A'); + QVERIFY(!fakeFolder.syncOnce()); + + // Mismatched OC-Checksum and X-Recalculate-Hash is supported, but, recalculated checksum is again mismatched -> sync must fail + isChecksumRecalculateSupported = true; + checksumValue = mismatchedSha1Checksum; + checksumValueRecalculated = mismatchedSha1Checksum; + QVERIFY(!fakeFolder.syncOnce()); + + // Mismatched OC-Checksum and X-Recalculate-Hash is supported, and, recalculated checksum is a match -> sync must succeed + isChecksumRecalculateSupported = true; + checksumValue = mismatchedSha1Checksum; + checksumValueRecalculated = matchedSha1Checksum; + QVERIFY(fakeFolder.syncOnce()); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + checksumValue = QByteArray(); + + fakeFolder.account()->setServerVersion(prevServerVersion); + // End Test mismatch recalculation----------------------------------------------------------------------------------- } // Tests the behavior of invalid filename detection |