Просмотр исходного кода

Ask server to recalculate checksum on validatin failure.

Signed-off-by: alex-z <blackslayer4@gmail.com>
alex-z 4 лет назад
Родитель
Сommit
b7be10f712

+ 16 - 3
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);

+ 13 - 1
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;
 };
 
 /**

+ 6 - 0
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) {

+ 2 - 0
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; }

+ 19 - 0
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)

+ 18 - 0
src/libsync/networkjobs.h

@@ -498,6 +498,24 @@ private slots:
     bool finished() override;
 };
 
+/**
+ * @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
  *

+ 49 - 6
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()

+ 2 - 1
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();

+ 1 - 0
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

+ 4 - 3
test/testchecksumvalidator.cpp

@@ -42,10 +42,11 @@ using namespace OCC::Utility;
          _successDown = true;
     }
 
-    void slotDownError(const QString &errMsg)
+    void slotDownError(const QString &errMsg, ValidateChecksumHeader::FailureReason reason)
     {
-         QCOMPARE(_expectedError, errMsg);
-         _errorSeen = true;
+        Q_UNUSED(reason);
+        QCOMPARE(_expectedError, errMsg);
+        _errorSeen = true;
     }
 
     static QByteArray shellSum( const QByteArray& cmd, const QString& file )