Преглед на файлове

Add a class TransmissionChecksumValidator, incl. unit test.

This does all needed to manage checksums that go with http headers
ensuring that the transmission was correct.
Klaas Freitag преди 10 години
родител
ревизия
830daa40d1
променени са 5 файла, в които са добавени 367 реда и са изтрити 0 реда
  1. 1 0
      src/libsync/CMakeLists.txt
  2. 152 0
      src/libsync/transmissionchecksumvalidator.cpp
  3. 54 0
      src/libsync/transmissionchecksumvalidator.h
  4. 1 0
      test/CMakeLists.txt
  5. 159 0
      test/testtranschecksumvalidator.h

+ 1 - 0
src/libsync/CMakeLists.txt

@@ -61,6 +61,7 @@ set(libsync_SRCS
     theme.cpp
     utility.cpp
     ownsql.cpp
+    transmissionchecksumvalidator.cpp
     creds/dummycredentials.cpp
     creds/abstractcredentials.cpp
     creds/credentialsfactory.cpp

+ 152 - 0
src/libsync/transmissionchecksumvalidator.cpp

@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) by Klaas Freitag <freitag@owncloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "config.h"
+#include "filesystem.h"
+#include "transmissionchecksumvalidator.h"
+#include "syncfileitem.h"
+#include "propagatorjobs.h"
+#include "configfile.h"
+
+#include <QtConcurrent>
+
+namespace OCC {
+
+TransmissionChecksumValidator::TransmissionChecksumValidator(const QString& filePath, QObject *parent)
+  :QObject(parent),
+    _filePath(filePath)
+{
+
+}
+
+void TransmissionChecksumValidator::setChecksumType( const QByteArray& type )
+{
+    _checksumType = type;
+}
+
+QString TransmissionChecksumValidator::checksumType()
+{
+    return _checksumType;
+}
+
+
+void TransmissionChecksumValidator::uploadValidation( SyncFileItem *item )
+{
+    QString checksumType = _checksumType;
+    if( checksumType.isEmpty() ) {
+        ConfigFile cfg;
+        checksumType = cfg.transmissionChecksum();
+    }
+
+    if( checksumType.isEmpty() || !item ) {
+        // if there is no checksum defined, continue to upload
+        emit validated();
+    } else {
+        _item = item;
+        // Calculate the checksum in a different thread first.
+        connect( &_watcher, SIGNAL(finished()),
+                 this, SLOT(slotUploadChecksumCalculated()));
+        if( checksumType == checkSumMD5C ) {
+            item->_checksum = checkSumMD5C;
+            item->_checksum += ":";
+            _watcher.setFuture(QtConcurrent::run(FileSystem::calcMd5Worker, _filePath));
+
+        } else if( checksumType == checkSumSHA1C ) {
+            item->_checksum = checkSumSHA1C;
+            item->_checksum += ":";
+            _watcher.setFuture(QtConcurrent::run( FileSystem::calcSha1Worker, _filePath));
+        }
+#ifdef ZLIB_FOUND
+        else if( checksumType == checkSumAdlerC) {
+            item->_checksum = checkSumAdlerC;
+            item->_checksum += ":";
+            _watcher.setFuture(QtConcurrent::run(FileSystem::calcAdler32Worker, _filePath));
+        }
+#endif
+        else {
+            // for an unknown checksum, continue to upload
+            emit validated();
+        }
+    }
+}
+
+void TransmissionChecksumValidator::slotUploadChecksumCalculated( )
+{
+    QByteArray checksum = _watcher.future().result();
+
+    if( !checksum.isEmpty() ) {
+        _item->_checksum.append(checksum);
+    } else {
+        _item->_checksum.clear();
+    }
+
+    emit validated();
+}
+
+
+void TransmissionChecksumValidator::downloadValidation( const QByteArray& checksumHeader )
+{
+    // if the incoming header is empty, there was no checksum header, and
+    // no validation can happen. Just continue.
+    if( checksumHeader.isEmpty() ) {
+        emit validated();
+        return;
+    }
+
+    bool ok = true;
+
+    int indx = checksumHeader.indexOf(':');
+    if( indx < 0 ) {
+        qDebug() << "Checksum header malformed:" << checksumHeader;
+        emit validated(); // show must go on - even not validated.
+    }
+
+    if( ok ) {
+        const QByteArray type = checksumHeader.left(indx).toUpper();
+        _expectedHash = checksumHeader.mid(indx+1);
+
+        connect( &_watcher, SIGNAL(finished()), this, SLOT(slotDownloadChecksumCalculated()) );
+
+        // start the calculation in different thread
+        if( type == checkSumMD5C ) {
+            _watcher.setFuture(QtConcurrent::run(FileSystem::calcMd5Worker, _filePath));
+        } else if( type == checkSumSHA1C ) {
+            _watcher.setFuture(QtConcurrent::run(FileSystem::calcSha1Worker, _filePath));
+        }
+#ifdef ZLIB_FOUND
+        else if( type == checkSumAdlerUpperC ) {
+            _watcher.setFuture(QtConcurrent::run(FileSystem::calcAdler32Worker, _filePath));
+        }
+#endif
+        else {
+            qDebug() << "Unknown checksum type" << type;
+            emit validationFailed(tr("The checksum header was malformed."));
+            return;
+        }
+    }
+}
+
+void TransmissionChecksumValidator::slotDownloadChecksumCalculated()
+{
+    const QByteArray hash = _watcher.future().result();
+
+    if( hash != _expectedHash ) {
+        emit validationFailed(tr("The file downloaded with a broken checksum, will be redownloaded."));
+    } else {
+        qDebug() << "Checksum checked and matching: " << _expectedHash;
+        emit validated();
+    }
+}
+
+
+}

+ 54 - 0
src/libsync/transmissionchecksumvalidator.h

@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) by Klaas Freitag <freitag@owncloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#pragma once
+
+#include <QObject>
+#include <QByteArray>
+#include <QFutureWatcher>
+
+namespace OCC {
+class SyncFileItem;
+
+class TransmissionChecksumValidator : public QObject
+{
+    Q_OBJECT
+public:
+    explicit TransmissionChecksumValidator(const QString& filePath, QObject *parent = 0);
+
+    void uploadValidation( SyncFileItem *item );
+    void downloadValidation( const QByteArray& checksumHeader );
+
+    void setChecksumType(const QByteArray &type );
+    QString checksumType();
+
+signals:
+    void validated();
+    void validationFailed( const QString& errMsg );
+
+private slots:
+    void slotUploadChecksumCalculated();
+    void slotDownloadChecksumCalculated();
+
+private:
+    QByteArray    _checksumType;
+    QByteArray    _expectedHash;
+    QString       _filePath;
+    SyncFileItem *_item;
+
+    // watcher for the checksum calculation thread
+    QFutureWatcher<QByteArray> _watcher;
+};
+
+}

+ 1 - 0
test/CMakeLists.txt

@@ -34,4 +34,5 @@ owncloud_add_test(ConcatUrl "")
 
 owncloud_add_test(XmlParse "")
 owncloud_add_test(FileSystem "")
+owncloud_add_test(TransChecksumValidator "")
 

+ 159 - 0
test/testtranschecksumvalidator.h

@@ -0,0 +1,159 @@
+/*
+ * This software is in the public domain, furnished "as is", without technical
+ * support, and with no warranty, express or implied, as to its usefulness for
+ * any purpose.
+ *
+ */
+
+#pragma once
+
+#include <QtTest>
+#include <QDir>
+#include <QString>
+
+#include "transmissionchecksumvalidator.h"
+#include "networkjobs.h"
+#include "syncfileitem.h"
+#include "utility.h"
+#include "filesystem.h"
+#include "propagatorjobs.h"
+
+using namespace OCC;
+
+    class TestTransChecksumValidator : public QObject
+    {
+        Q_OBJECT
+
+    private:
+        QString _root;
+        QString _testfile;
+        QString _expectedError;
+        SyncFileItem *_item;
+        QEventLoop     _loop;
+        QByteArray     _expected;
+        bool           _successDown;
+        bool           _errorSeen;
+
+    void processAndWait() {
+            _loop.processEvents();
+            Utility::usleep(200000);
+            _loop.processEvents();
+    }
+
+    public slots:
+
+    void slotUpValidated() {
+         qDebug() << "Checksum: " << _item->_checksum;
+         QVERIFY(_expected == _item->_checksum );
+    }
+
+    void slotDownValidated() {
+         _successDown = true;
+    }
+
+    void slotDownError( const QString& errMsg ) {
+         QVERIFY(_expectedError == errMsg );
+         _errorSeen = true;
+    }
+
+    private slots:
+
+    void initTestCase() {
+        qDebug() << Q_FUNC_INFO;
+        _root = QDir::tempPath() + "/" + "test_" + QString::number(qrand());
+        QDir rootDir(_root);
+
+        rootDir.mkpath(_root );
+        _testfile = _root+"/csFile";
+        Utility::writeRandomFile( _testfile);
+
+        _item = new SyncFileItem;
+    }
+
+    void testUploadChecksummingAdler() {
+
+        TransmissionChecksumValidator *vali = new TransmissionChecksumValidator(_testfile);
+        connect(vali, SIGNAL(validated()), this, SLOT(slotUpValidated()));
+
+        _expected = "Adler32:"+FileSystem::calcAdler32( _testfile );
+        qDebug() << "XX Expected Checksum: " << _expected;
+        vali->uploadValidation(_item);
+
+        usleep(5000);
+
+        _loop.processEvents();
+        vali->deleteLater();
+    }
+
+    void testUploadChecksummingMd5() {
+
+        TransmissionChecksumValidator *vali = new TransmissionChecksumValidator(_testfile);
+        vali->setChecksumType( OCC::checkSumMD5C );
+        connect(vali, SIGNAL(validated()), this, SLOT(slotUpValidated()));
+
+        _expected = checkSumMD5C;
+        _expected.append(":"+FileSystem::calcMd5( _testfile ));
+        vali->uploadValidation(_item);
+
+        usleep(2000);
+
+        _loop.processEvents();
+        vali->deleteLater();
+    }
+
+    void testUploadChecksummingSha1() {
+
+        TransmissionChecksumValidator *vali = new TransmissionChecksumValidator(_testfile);
+        vali->setChecksumType( OCC::checkSumSHA1C );
+        connect(vali, SIGNAL(validated()), this, SLOT(slotUpValidated()));
+
+        _expected = checkSumSHA1C;
+        _expected.append(":"+FileSystem::calcSha1( _testfile ));
+
+        vali->uploadValidation(_item);
+
+        usleep(2000);
+
+        _loop.processEvents();
+        vali->deleteLater();
+    }
+
+    void testDownloadChecksummingAdler() {
+
+        QByteArray adler =  checkSumAdlerC;
+        adler.append(":");
+        adler.append(FileSystem::calcAdler32( _testfile ));
+        _successDown = false;
+
+        TransmissionChecksumValidator *vali = new TransmissionChecksumValidator(_testfile);
+        connect(vali, SIGNAL(validated()), this, SLOT(slotDownValidated()));
+        connect(vali, SIGNAL(validationFailed(QString)), this, SLOT(slotDownError(QString)));
+        vali->downloadValidation(adler);
+
+        usleep(2000);
+
+        _loop.processEvents();
+        QVERIFY(_successDown);
+
+        _expectedError = QLatin1String("The file downloaded with a broken checksum, will be redownloaded.");
+        _errorSeen = false;
+        vali->downloadValidation("Adler32:543345");
+        usleep(2000);
+        _loop.processEvents();
+        QVERIFY(_errorSeen);
+
+        _expectedError = QLatin1String("The checksum header was malformed.");
+        _errorSeen = false;
+        vali->downloadValidation("Klaas32:543345");
+        usleep(2000);
+        _loop.processEvents();
+        QVERIFY(_errorSeen);
+
+        vali->deleteLater();
+    }
+
+
+    void cleanupTestCase() {
+        delete _item;
+    }
+};