diff options
author | alex-z <blackslayer4@gmail.com> | 2021-12-20 12:13:48 +0300 |
---|---|---|
committer | allexzander (Rebase PR Action) <allexzander@users.noreply.github.com> | 2022-01-11 16:37:09 +0300 |
commit | b7be10f712a5e4046f443460b95916abef9ebdae (patch) | |
tree | c58d8fa96eb19a0fda87edf7c7c7544762d000ed /src | |
parent | fbe35538b667cf444645ed82ffa52f1a91a90809 (diff) |
Ask server to recalculate checksum on validatin failure.
Signed-off-by: alex-z <blackslayer4@gmail.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/common/checksums.cpp | 19 | ||||
-rw-r--r-- | src/common/checksums.h | 14 | ||||
-rw-r--r-- | src/libsync/account.cpp | 6 | ||||
-rw-r--r-- | src/libsync/account.h | 2 | ||||
-rw-r--r-- | src/libsync/networkjobs.cpp | 19 | ||||
-rw-r--r-- | src/libsync/networkjobs.h | 18 | ||||
-rw-r--r-- | src/libsync/propagatedownload.cpp | 55 | ||||
-rw-r--r-- | src/libsync/propagatedownload.h | 3 | ||||
-rw-r--r-- | src/libsync/propagatorjobs.h | 1 |
9 files changed, 126 insertions, 11 deletions
diff --git a/src/common/checksums.cpp b/src/common/checksums.cpp index 0c016ef4d..5cef55bfb 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.")); + emit validationFailed(tr("The checksum header is malformed."), ChecksumHeaderMalformed); return nullptr; } @@ -360,15 +360,28 @@ void ValidateChecksumHeader::start(std::unique_ptr<QIODevice> device, const QByt calculator->start(std::move(device)); } +QByteArray ValidateChecksumHeader::calculatedChecksumType() const +{ + return _calculatedChecksumType; +} + +QByteArray ValidateChecksumHeader::calculatedChecksum() const +{ + return _calculatedChecksum; +} + void ValidateChecksumHeader::slotChecksumCalculated(const QByteArray &checksumType, const QByteArray &checksum) { + _calculatedChecksumType = checksumType; + _calculatedChecksum = checksum; + if (checksumType != _expectedChecksumType) { - emit validationFailed(tr("The checksum header contained an unknown checksum type \"%1\"").arg(QString::fromLatin1(_expectedChecksumType))); + emit validationFailed(tr("The checksum header contained an unknown checksum type \"%1\"").arg(QString::fromLatin1(_expectedChecksumType)), 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))); + 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); return; } emit validated(checksumType, checksum); diff --git a/src/common/checksums.h b/src/common/checksums.h index 351fa745d..80716040c 100644 --- a/src/common/checksums.h +++ b/src/common/checksums.h @@ -140,6 +140,12 @@ class OCSYNC_EXPORT ValidateChecksumHeader : public QObject { Q_OBJECT public: + enum FailureReason { + Success, + ChecksumHeaderMalformed, + ChecksumTypeUnknown, + ChecksumMismatch, + }; explicit ValidateChecksumHeader(QObject *parent = nullptr); /** @@ -161,9 +167,12 @@ public: */ void start(std::unique_ptr<QIODevice> device, const QByteArray &checksumHeader); + QByteArray calculatedChecksumType() const; + QByteArray calculatedChecksum() const; + signals: void validated(const QByteArray &checksumType, const QByteArray &checksum); - void validationFailed(const QString &errMsg); + void validationFailed(const QString &errMsg, FailureReason reason); private slots: void slotChecksumCalculated(const QByteArray &checksumType, const QByteArray &checksum); @@ -173,6 +182,9 @@ private: QByteArray _expectedChecksumType; QByteArray _expectedChecksum; + + QByteArray _calculatedChecksumType; + QByteArray _calculatedChecksum; }; /** diff --git a/src/libsync/account.cpp b/src/libsync/account.cpp index 537bd71e4..63a018709 100644 --- a/src/libsync/account.cpp +++ b/src/libsync/account.cpp @@ -58,6 +58,7 @@ using namespace QKeychain; namespace { constexpr int pushNotificationsReconnectInterval = 1000 * 60 * 2; constexpr int usernamePrefillServerVersinMinSupportedMajor = 24; +constexpr int checksumRecalculateRequestServerVersionMinSupportedMajor = 24; } namespace OCC { @@ -635,6 +636,11 @@ bool Account::isUsernamePrefillSupported() const return serverVersionInt() >= makeServerVersion(usernamePrefillServerVersinMinSupportedMajor, 0, 0); } +bool Account::isChecksumRecalculateRequestSupported() const +{ + return serverVersionInt() >= makeServerVersion(checksumRecalculateRequestServerVersionMinSupportedMajor, 0, 0); +} + void Account::setServerVersion(const QString &version) { if (version == _serverVersion) { diff --git a/src/libsync/account.h b/src/libsync/account.h index 03e8a88cc..b1044f4d4 100644 --- a/src/libsync/account.h +++ b/src/libsync/account.h @@ -232,6 +232,8 @@ public: bool isUsernamePrefillSupported() const; + bool isChecksumRecalculateRequestSupported() 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 f2ba661e4..5e29ab8c4 100644 --- a/src/libsync/networkjobs.cpp +++ b/src/libsync/networkjobs.cpp @@ -1084,6 +1084,25 @@ bool SimpleNetworkJob::finished() return true; } +SimpleFileManipulationNetworkJob::SimpleFileManipulationNetworkJob(AccountPtr account, const QString &filePath, QObject *parent) + : AbstractNetworkJob(account, filePath, parent) +{ +} + +QNetworkReply *SimpleFileManipulationNetworkJob::startRequest( + const QByteArray &verb, QNetworkRequest req, QIODevice *requestBody) +{ + const auto davUrlString = makeDavUrl(path()).toString(); + auto reply = sendRequest(verb, makeDavUrl(path()), req, requestBody); + start(); + return reply; +} + +bool SimpleFileManipulationNetworkJob::finished() +{ + emit finishedSignal(reply()); + return true; +} DeleteApiJob::DeleteApiJob(AccountPtr account, const QString &path, QObject *parent) : AbstractNetworkJob(account, path, parent) diff --git a/src/libsync/networkjobs.h b/src/libsync/networkjobs.h index 01cfcdedd..037379521 100644 --- a/src/libsync/networkjobs.h +++ b/src/libsync/networkjobs.h @@ -499,6 +499,24 @@ private slots: }; /** + * @brief A basic file manipulation job + * @ingroup libsync + */ +class OWNCLOUDSYNC_EXPORT SimpleFileManipulationNetworkJob : public AbstractNetworkJob +{ + Q_OBJECT +public: + explicit SimpleFileManipulationNetworkJob(AccountPtr account, const QString &filePath, QObject *parent = nullptr); + + QNetworkReply *startRequest(const QByteArray &verb, QNetworkRequest req = QNetworkRequest(), QIODevice *requestBody = nullptr); + +signals: + void finishedSignal(QNetworkReply *reply); +private slots: + bool finished() override; +}; + +/** * @brief Runs a PROPFIND to figure out the private link url * * The numericFileId is used only to build the deprecatedPrivateLinkUrl diff --git a/src/libsync/propagatedownload.cpp b/src/libsync/propagatedownload.cpp index 93a204d34..b3790916b 100644 --- a/src/libsync/propagatedownload.cpp +++ b/src/libsync/propagatedownload.cpp @@ -22,7 +22,6 @@ #include "common/utility.h" #include "filesystem.h" #include "propagatorjobs.h" -#include <common/checksums.h> #include <common/asserts.h> #include <common/constants.h> #include "clientsideencryptionjobs.h" @@ -923,11 +922,55 @@ void PropagateDownloadFile::slotGetFinished() validator->start(_tmpFile.fileName(), checksumHeader); } -void PropagateDownloadFile::slotChecksumFail(const QString &errMsg) -{ - FileSystem::remove(_tmpFile.fileName()); - propagator()->_anotherSyncNeeded = true; - done(SyncFileItem::SoftError, errMsg); // tr("The file downloaded with a broken checksum, will be redownloaded.")); +void PropagateDownloadFile::slotChecksumFail(const QString &errMsg, ValidateChecksumHeader::FailureReason reason) +{ + const auto processChecksumFailure = [this, errMsg]() { + FileSystem::remove(_tmpFile.fileName()); + propagator()->_anotherSyncNeeded = true; + done(SyncFileItem::SoftError, errMsg); // tr("The file downloaded with a broken checksum, will be redownloaded.")); + }; + + if (reason == ValidateChecksumHeader::FailureReason::ChecksumMismatch + && propagator()->account()->isChecksumRecalculateRequestSupported()) { + if (const auto validator = qobject_cast<ValidateChecksumHeader *>(sender())) { + const QByteArray calculatedChecksum(validator->calculatedChecksumType() + ':' + validator->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) { + if (reply->error() == QNetworkReply::NoError) { + const auto newChecksumFromServer = reply->rawHeader(checkSumHeaderC); + if (newChecksumFromServer == calculatedChecksum) { + const auto newChecksumFromServerSplit = newChecksumFromServer.split(':'); + if (newChecksumFromServerSplit.size() > 1) { + transmissionChecksumValidated( + newChecksumFromServerSplit.first(), newChecksumFromServerSplit.last()); + return; + } + } + + qCCritical(lcPropagateDownload) << "Checksum recalculation has failed for file:" << reply->url() + << " " << checkSumHeaderC << " received is:" << newChecksumFromServer; + } + + if (reply->error() != QNetworkReply::NoError) { + qCCritical(lcPropagateDownload) + << "Checksum recalculation has failed for file:" << reply->url() + << " Recalculation request has finished with error:" << reply->errorString(); + } + + processChecksumFailure(); + }); + + qCWarning(lcPropagateDownload) << "Checksum validation has failed for file:" << fullRemotePathForFile + << " Requesting checksum recalculation on the server..."; + QNetworkRequest req; + req.setRawHeader(checksumRecalculateOnServer, validator->calculatedChecksumType()); + job->startRequest(QByteArrayLiteral("PATCH"), req); + return; + } + } + + processChecksumFailure(); } void PropagateDownloadFile::deleteExistingFolder() diff --git a/src/libsync/propagatedownload.h b/src/libsync/propagatedownload.h index ba9fff195..ce13f879c 100644 --- a/src/libsync/propagatedownload.h +++ b/src/libsync/propagatedownload.h @@ -17,6 +17,7 @@ #include "owncloudpropagator.h" #include "networkjobs.h" #include "clientsideencryption.h" +#include <common/checksums.h> #include <QBuffer> #include <QFile> @@ -235,7 +236,7 @@ private slots: void abort(PropagatorJob::AbortType abortType) override; void slotDownloadProgress(qint64, qint64); - void slotChecksumFail(const QString &errMsg); + void slotChecksumFail(const QString &errMsg, ValidateChecksumHeader::FailureReason reason); private: void startAfterIsEncryptedIsChecked(); diff --git a/src/libsync/propagatorjobs.h b/src/libsync/propagatorjobs.h index 1a1266865..1fa844378 100644 --- a/src/libsync/propagatorjobs.h +++ b/src/libsync/propagatorjobs.h @@ -26,6 +26,7 @@ namespace OCC { */ static const char checkSumHeaderC[] = "OC-Checksum"; static const char contentMd5HeaderC[] = "Content-MD5"; +static const char checksumRecalculateOnServer[] = "X-Recalculate-Hash"; /** * @brief Declaration of the other propagation jobs |