From 830daa40d1dae9064975e20c9295660fb8337fce Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Fri, 15 May 2015 15:34:17 +0200 Subject: [PATCH] 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. --- src/libsync/CMakeLists.txt | 1 + src/libsync/transmissionchecksumvalidator.cpp | 152 +++++++++++++++++ src/libsync/transmissionchecksumvalidator.h | 54 ++++++ test/CMakeLists.txt | 1 + test/testtranschecksumvalidator.h | 159 ++++++++++++++++++ 5 files changed, 367 insertions(+) create mode 100644 src/libsync/transmissionchecksumvalidator.cpp create mode 100644 src/libsync/transmissionchecksumvalidator.h create mode 100644 test/testtranschecksumvalidator.h diff --git a/src/libsync/CMakeLists.txt b/src/libsync/CMakeLists.txt index aaa1d93ca..7247d16a6 100644 --- a/src/libsync/CMakeLists.txt +++ b/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 diff --git a/src/libsync/transmissionchecksumvalidator.cpp b/src/libsync/transmissionchecksumvalidator.cpp new file mode 100644 index 000000000..e6fd6f338 --- /dev/null +++ b/src/libsync/transmissionchecksumvalidator.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (C) by Klaas Freitag + * + * 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 + +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(); + } +} + + +} diff --git a/src/libsync/transmissionchecksumvalidator.h b/src/libsync/transmissionchecksumvalidator.h new file mode 100644 index 000000000..dc9f7effc --- /dev/null +++ b/src/libsync/transmissionchecksumvalidator.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) by Klaas Freitag + * + * 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 +#include +#include + +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 _watcher; +}; + +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ac3b34e33..ef64f5d2d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -34,4 +34,5 @@ owncloud_add_test(ConcatUrl "") owncloud_add_test(XmlParse "") owncloud_add_test(FileSystem "") +owncloud_add_test(TransChecksumValidator "") diff --git a/test/testtranschecksumvalidator.h b/test/testtranschecksumvalidator.h new file mode 100644 index 000000000..6d3c37ca4 --- /dev/null +++ b/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 +#include +#include + +#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; + } +}; -- 2.30.2