diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ff58de73..da6d004e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -165,13 +165,13 @@ endif() find_package(Sphinx) find_package(PdfLatex) - find_package(SQLite3 3.8.0 REQUIRED) # On some OS, we want to use our own, not the system sqlite if (USE_OUR_OWN_SQLITE3) include_directories(BEFORE ${SQLITE3_INCLUDE_DIR}) endif() +find_package(ZLIB) configure_file(config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) diff --git a/ChangeLog b/ChangeLog index d4f100a01..3675841de 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ ChangeLog ========= +version 1.8.2 (release 2015-06-xx) + * HTTP: Add the branding name to the UserAgent string. + version 1.8.1 (release 2015-05-07) * Make "operation canceled" error a soft error * Do not throw an error for files that are scheduled to be removed, diff --git a/cmake/modules/QtVersionAbstraction.cmake b/cmake/modules/QtVersionAbstraction.cmake index 020aab4ea..760e9ed3a 100644 --- a/cmake/modules/QtVersionAbstraction.cmake +++ b/cmake/modules/QtVersionAbstraction.cmake @@ -17,6 +17,7 @@ if( Qt5Core_FOUND ) message(STATUS "Found Qt5 core, checking for further dependencies...") find_package(Qt5Network REQUIRED) find_package(Qt5Xml REQUIRED) + find_package(Qt5Concurrent REQUIRED) if(NOT TOKEN_AUTH_ONLY) find_package(Qt5WebKitWidgets REQUIRED) find_package(Qt5WebKit REQUIRED) diff --git a/config.h.in b/config.h.in index 48e9d8852..601dc6be3 100644 --- a/config.h.in +++ b/config.h.in @@ -19,6 +19,8 @@ #cmakedefine APPLICATION_EXECUTABLE "@APPLICATION_EXECUTABLE@" #cmakedefine APPLICATION_UPDATE_URL "@APPLICATION_UPDATE_URL@" +#cmakedefine ZLIB_FOUND @ZLIB_FOUND@ + #cmakedefine SYSCONFDIR "@SYSCONFDIR@" #cmakedefine DATADIR "@DATADIR@" diff --git a/src/libsync/CMakeLists.txt b/src/libsync/CMakeLists.txt index e926f1b78..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 @@ -141,6 +142,11 @@ if(NEON_FOUND) endif() endif() +if(ZLIB_FOUND) + list(APPEND libsync_LINK_TARGETS ${ZLIB_LIBRARIES}) + include_directories(${ZLIB_INCLUDE_DIRS}) +endif(ZLIB_FOUND) + add_library(${synclib_NAME} SHARED ${libsync_SRCS} ${syncMoc}) GENERATE_EXPORT_HEADER( ${synclib_NAME} BASE_NAME ${synclib_NAME} @@ -151,9 +157,9 @@ GENERATE_EXPORT_HEADER( ${synclib_NAME} if(TOKEN_AUTH_ONLY) - qt5_use_modules(${synclib_NAME} Network) + qt5_use_modules(${synclib_NAME} Network Concurrent) else() - qt5_use_modules(${synclib_NAME} Widgets Network WebKitWidgets) + qt5_use_modules(${synclib_NAME} Widgets Network WebKitWidgets Concurrent) endif() set_target_properties( ${synclib_NAME} PROPERTIES diff --git a/src/libsync/configfile.cpp b/src/libsync/configfile.cpp index d8a3bf3f6..2b04b1131 100644 --- a/src/libsync/configfile.cpp +++ b/src/libsync/configfile.cpp @@ -49,6 +49,7 @@ static const char optionalDesktopNoficationsC[] = "optionalDesktopNotifications" static const char skipUpdateCheckC[] = "skipUpdateCheck"; static const char geometryC[] = "geometry"; static const char timeoutC[] = "timeout"; +static const char transmissionChecksumC[] = "transmissionChecksum"; static const char proxyHostC[] = "Proxy/host"; static const char proxyTypeC[] = "Proxy/type"; @@ -118,6 +119,20 @@ int ConfigFile::timeout() const return settings.value(QLatin1String(timeoutC), 300).toInt(); // default to 5 min } +QString ConfigFile::transmissionChecksum() const +{ + QSettings settings(configFile(), QSettings::IniFormat); + + QString checksum = settings.value(QLatin1String(transmissionChecksumC), QString()).toString(); + + if( checksum.isEmpty() ) { + // if the config file setting is empty, maybe the Branding requires it. + checksum = Theme::instance()->transmissionChecksum(); + } + + return checksum; +} + void ConfigFile::setOptionalDesktopNotifications(bool show) { QSettings settings(configFile(), QSettings::IniFormat); diff --git a/src/libsync/configfile.h b/src/libsync/configfile.h index 54652b6c9..d1af84c55 100644 --- a/src/libsync/configfile.h +++ b/src/libsync/configfile.h @@ -103,6 +103,12 @@ public: int timeout() const; + // send a checksum as a header along with the transmission or not. + // possible values: + // empty: no checksum calculated or expected. + // or "Adler32", "MD5", "SHA1" + QString transmissionChecksum() const; + void saveGeometry(QWidget *w); void restoreGeometry(QWidget *w); diff --git a/src/libsync/filesystem.cpp b/src/libsync/filesystem.cpp index 4191686d0..b3fb52657 100644 --- a/src/libsync/filesystem.cpp +++ b/src/libsync/filesystem.cpp @@ -18,6 +18,11 @@ #include #include #include +#include + +#ifdef ZLIB_FOUND +#include +#endif #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) #include @@ -394,4 +399,58 @@ QString FileSystem::fileSystemForPath(const QString & path) } #endif +#define BUFSIZE 1024*1024*10 + +static QByteArray readToCrypto( const QString& filename, QCryptographicHash::Algorithm algo ) +{ + const qint64 bufSize = BUFSIZE; + QByteArray buf(bufSize,0); + QByteArray arr; + QCryptographicHash crypto( algo ); + + QFile file(filename); + if (file.open(QIODevice::ReadOnly)) { + qint64 size; + while (!file.atEnd()) { + size = file.read( buf.data(), bufSize ); + if( size > 0 ) { + crypto.addData(buf.data(), size); + } + } + arr = crypto.result().toHex(); + } + return arr; +} + +QByteArray FileSystem::calcMd5( const QString& filename ) +{ + return readToCrypto( filename, QCryptographicHash::Md5 ); +} + +QByteArray FileSystem::calcSha1( const QString& filename ) +{ + return readToCrypto( filename, QCryptographicHash::Sha1 ); +} + +#ifdef ZLIB_FOUND +QByteArray FileSystem::calcAdler32( const QString& filename ) +{ + unsigned int adler = adler32(0L, Z_NULL, 0); + const qint64 bufSize = BUFSIZE; + QByteArray buf(bufSize, 0); + + QFile file(filename); + if (file.open(QIODevice::ReadOnly)) { + qint64 size; + while (!file.atEnd()) { + size = file.read(buf.data(), bufSize); + if( size > 0 ) + adler = adler32(adler, (const Bytef*) buf.data(), size); + } + } + + return QByteArray::number( adler, 16 ); +} +#endif + } // namespace OCC diff --git a/src/libsync/filesystem.h b/src/libsync/filesystem.h index ac51e6395..9e314a191 100644 --- a/src/libsync/filesystem.h +++ b/src/libsync/filesystem.h @@ -13,8 +13,11 @@ #pragma once +#include "config.h" + #include #include +#include #include @@ -121,4 +124,10 @@ bool openAndSeekFileSharedRead(QFile* file, QString* error, qint64 seek); QString fileSystemForPath(const QString & path); #endif +QByteArray calcMd5( const QString& fileName ); +QByteArray calcSha1( const QString& fileName ); +#ifdef ZLIB_FOUND +QByteArray calcAdler32( const QString& fileName ); +#endif + }} diff --git a/src/libsync/networkjobs.cpp b/src/libsync/networkjobs.cpp index 78e9d28b0..ed0aee9ea 100644 --- a/src/libsync/networkjobs.cpp +++ b/src/libsync/networkjobs.cpp @@ -43,7 +43,6 @@ namespace OCC { AbstractNetworkJob::AbstractNetworkJob(AccountPtr account, const QString &path, QObject *parent) : QObject(parent) - , _duration(0) , _timedout(false) , _followRedirects(false) , _ignoreCredentialFailure(false) diff --git a/src/libsync/owncloudpropagator.cpp b/src/libsync/owncloudpropagator.cpp index 1ccefaf59..ff9beaa1e 100644 --- a/src/libsync/owncloudpropagator.cpp +++ b/src/libsync/owncloudpropagator.cpp @@ -259,6 +259,21 @@ void OwncloudPropagator::start(const SyncFileItemVector& items) { Q_ASSERT(std::is_sorted(items.begin(), items.end())); + /* Check and log the transmission checksum type */ + ConfigFile cfg; + const QString checksumType = cfg.transmissionChecksum().toUpper(); + + /* if the checksum type is empty, it is not send. No error */ + if( !checksumType.isEmpty() ) { + if( checksumType == checkSumAdlerUpperC || + checksumType == checkSumMD5C || + checksumType == checkSumSHA1C ) { + qDebug() << "Client sends and expects transmission checksum type" << checksumType; + } else { + qWarning() << "Unknown transmission checksum type from config" << checksumType; + } + } + /* This builds all the job needed for the propagation. * Each directories is a PropagateDirectory job, which contains the files in it. * In order to do that we loop over the items. (which are sorted by destination) diff --git a/src/libsync/propagatedownload.cpp b/src/libsync/propagatedownload.cpp index 42c45ae76..73815d1cf 100644 --- a/src/libsync/propagatedownload.cpp +++ b/src/libsync/propagatedownload.cpp @@ -12,6 +12,7 @@ * for more details. */ +#include "config.h" #include "owncloudpropagator_p.h" #include "propagatedownload.h" #include "networkjobs.h" @@ -21,6 +22,8 @@ #include "utility.h" #include "filesystem.h" #include "propagatorjobs.h" +#include "transmissionchecksumvalidator.h" + #include #include #include @@ -483,7 +486,21 @@ void PropagateDownloadFileQNAM::slotGetFinished() return; } - downloadFinished(); + // Do checksum validation for the download. If there is no checksum header, the validator + // will also emit the validated() signal to continue the flow in slot downloadFinished() + // as this is (still) also correct. + TransmissionChecksumValidator *validator = new TransmissionChecksumValidator(_tmpFile.fileName(), this); + connect(validator, SIGNAL(validated(QByteArray)), this, SLOT(downloadFinished())); + connect(validator, SIGNAL(validationFailed(QString)), this, SLOT(slotChecksumFail(QString))); + validator->downloadValidation(job->reply()->rawHeader(checkSumHeaderC)); + +} + +void PropagateDownloadFileQNAM::slotChecksumFail( const QString& errMsg ) +{ + _tmpFile.remove(); + _propagator->_anotherSyncNeeded = true; + done(SyncFileItem::SoftError, errMsg ); // tr("The file downloaded with a broken checksum, will be redownloaded.")); } QString makeConflictFileName(const QString &fn, const QDateTime &dt) diff --git a/src/libsync/propagatedownload.h b/src/libsync/propagatedownload.h index 47c60702a..68a059ffb 100644 --- a/src/libsync/propagatedownload.h +++ b/src/libsync/propagatedownload.h @@ -101,19 +101,21 @@ private slots: class PropagateDownloadFileQNAM : public PropagateItemJob { Q_OBJECT - QPointer _job; - -// QFile *_file; - QFile _tmpFile; public: PropagateDownloadFileQNAM(OwncloudPropagator* propagator,const SyncFileItem& item) : PropagateItemJob(propagator, item) {} void start() Q_DECL_OVERRIDE; + private slots: void slotGetFinished(); void abort() Q_DECL_OVERRIDE; void downloadFinished(); void slotDownloadProgress(qint64,qint64); + void slotChecksumFail( const QString& errMsg ); + +private: + QPointer _job; + QFile _tmpFile; }; } diff --git a/src/libsync/propagateupload.cpp b/src/libsync/propagateupload.cpp index 980c36118..05e977434 100644 --- a/src/libsync/propagateupload.cpp +++ b/src/libsync/propagateupload.cpp @@ -12,6 +12,7 @@ * for more details. */ +#include "config.h" #include "propagateupload.h" #include "owncloudpropagator_p.h" #include "networkjobs.h" @@ -21,6 +22,8 @@ #include "utility.h" #include "filesystem.h" #include "propagatorjobs.h" +#include "transmissionchecksumvalidator.h" + #include #include #include @@ -190,22 +193,51 @@ bool PollJob::finished() return true; } - void PropagateUploadFileQNAM::start() { if (_propagator->_abortRequested.fetchAndAddRelaxed(0)) { return; } + const QString filePath = _propagator->getFilePath(_item._file); + + // remember the modtime before checksumming to be able to detect a file + // change during the checksum calculation + _item._modtime = FileSystem::getModTime(filePath); + + _stopWatch.start(); + + // do whatever is needed to add a checksum to the http upload request. + // in any case, the validator will emit signal startUpload to let the flow + // continue in slotStartUpload here. + TransmissionChecksumValidator *validator = new TransmissionChecksumValidator(filePath, this); + connect(validator, SIGNAL(validated(QByteArray)), this, SLOT(slotStartUpload(QByteArray))); + validator->uploadValidation(); +} + +void PropagateUploadFileQNAM::slotStartUpload(const QByteArray& checksum) +{ const QString fullFilePath(_propagator->getFilePath(_item._file)); + _item._checksum = checksum; + if (!FileSystem::fileExists(fullFilePath)) { done(SyncFileItem::SoftError, tr("File Removed")); return; } + _stopWatch.addLapTime(QLatin1String("Checksum")); + + time_t prevModtime = _item._modtime; // the _item value was set in PropagateUploadFileQNAM::start() + // but a potential checksum calculation could have taken some time during which the file could + // have been changed again, so better check again here. - // Update the mtime and size, it might have changed since discovery. _item._modtime = FileSystem::getModTime(fullFilePath); + if( prevModtime != _item._modtime ) { + _propagator->_anotherSyncNeeded = true; + done(SyncFileItem::SoftError, tr("Local file changed during syncing. It will be resumed.")); + return; + } + quint64 fileSize = FileSystem::getSize(fullFilePath); _item._size = fileSize; @@ -432,6 +464,14 @@ void PropagateUploadFileQNAM::startNextChunk() if( currentChunkSize == 0 ) { // if the last chunk pretents to be 0, its actually the full chunk size. currentChunkSize = chunkSize(); } + if( !_item._checksum.isEmpty() ) { + headers[checkSumHeaderC] = _item._checksum; + } + } + } else { + // checksum if its only one chunk + if( !_item._checksum.isEmpty() ) { + headers[checkSumHeaderC] = _item._checksum; } } @@ -652,6 +692,11 @@ void PropagateUploadFileQNAM::slotPutFinished() // Well, the mtime was not set #endif } + + // performance logging + _item._requestDuration = _stopWatch.stop(); + qDebug() << "*==* duration UPLOAD" << _item._size << _stopWatch.durationOfLap(QLatin1String("Checksum")) << _item._requestDuration; + finalize(_item); } diff --git a/src/libsync/propagateupload.h b/src/libsync/propagateupload.h index 4bfbe3036..2a34faa58 100644 --- a/src/libsync/propagateupload.h +++ b/src/libsync/propagateupload.h @@ -20,6 +20,7 @@ #include #include + namespace OCC { class BandwidthManager; @@ -75,6 +76,8 @@ protected slots: class PUTFileJob : public AbstractNetworkJob { Q_OBJECT + +private: QScopedPointer _device; QMap _headers; QString _errorString; @@ -146,6 +149,7 @@ signals: class PropagateUploadFileQNAM : public PropagateItemJob { Q_OBJECT +private: /** * That's the start chunk that was stored in the database for resuming. * In the non-resuming case it is 0. @@ -163,6 +167,10 @@ class PropagateUploadFileQNAM : public PropagateItemJob { QElapsedTimer _duration; QVector _jobs; /// network jobs that are currently in transit bool _finished; // Tells that all the jobs have been finished + + // measure the performance of checksum calc and upload + Utility::StopWatch _stopWatch; + public: PropagateUploadFileQNAM(OwncloudPropagator* propagator,const SyncFileItem& item) : PropagateItemJob(propagator, item), _startChunk(0), _currentChunk(0), _chunkCount(0), _transferId(0), _finished(false) {} @@ -175,6 +183,8 @@ private slots: void startNextChunk(); void finalize(const SyncFileItem&); void slotJobDestroyed(QObject *job); + void slotStartUpload(const QByteArray &checksum); + private: void startPollJob(const QString& path); void abortWithError(SyncFileItem::Status status, const QString &error); diff --git a/src/libsync/propagatorjobs.h b/src/libsync/propagatorjobs.h index 99343fe9b..d1b31d74e 100644 --- a/src/libsync/propagatorjobs.h +++ b/src/libsync/propagatorjobs.h @@ -21,6 +21,22 @@ namespace OCC { +/** + * Tags for checksum headers. + * They are here for being shared between Upload- and Download Job + */ + +// the header itself +static const char checkSumHeaderC[] = "OC-Checksum"; +// ...and it's values +static const char checkSumMD5C[] = "MD5"; +static const char checkSumSHA1C[] = "SHA1"; +static const char checkSumAdlerC[] = "Adler32"; +static const char checkSumAdlerUpperC[] = "ADLER32"; + +/** + * Declaration of the other propagation jobs + */ class PropagateLocalRemove : public PropagateItemJob { Q_OBJECT public: diff --git a/src/libsync/syncfileitem.h b/src/libsync/syncfileitem.h index dc2bf4aca..b764d73b7 100644 --- a/src/libsync/syncfileitem.h +++ b/src/libsync/syncfileitem.h @@ -150,6 +150,7 @@ public: quint64 _inode; QByteArray _fileId; QByteArray _remotePerm; + QByteArray _checksum; QString _directDownloadUrl; QString _directDownloadCookies; diff --git a/src/libsync/theme.cpp b/src/libsync/theme.cpp index 3a7849773..a388f3c4c 100644 --- a/src/libsync/theme.cpp +++ b/src/libsync/theme.cpp @@ -241,12 +241,17 @@ QString Theme::updateCheckUrl() const return QLatin1String("https://updates.owncloud.com/client/"); } +QString Theme::transmissionChecksum() const +{ + return QString::null; // No transmission by default. +} + QString Theme::gitSHA1() const { QString devString; #ifdef GIT_SHA1 const QString githubPrefix(QLatin1String( - "https://github.com/owncloud/mirall/commit/")); + "https://github.com/owncloud/client/commit/")); const QString gitSha1(QLatin1String(GIT_SHA1)); devString = QCoreApplication::translate("ownCloudTheme::about()", "

Built from Git revision %2" @@ -389,5 +394,5 @@ bool Theme::wizardSelectiveSyncDefaultNothing() const } -} // end namespace mirall +} // end namespace client diff --git a/src/libsync/theme.h b/src/libsync/theme.h index 975e23452..7f30950aa 100644 --- a/src/libsync/theme.h +++ b/src/libsync/theme.h @@ -189,12 +189,19 @@ public: */ virtual QString updateCheckUrl() const; - /** * When true, the setup wizard will show the selective sync dialog by default and default * to nothing selected */ virtual bool wizardSelectiveSyncDefaultNothing() const; + /** + * @brief Add an additional checksum header to PUT requests and compare them + * if they come with GET requests. + * This value sets the checksum type (SHA1, MD5 or Adler32) or is left empty + * if no checksumming is wanted. In that case it can still be overwritten in + * the client config file. + */ + virtual QString transmissionChecksum() const; protected: #ifndef TOKEN_AUTH_ONLY diff --git a/src/libsync/transmissionchecksumvalidator.cpp b/src/libsync/transmissionchecksumvalidator.cpp new file mode 100644 index 000000000..34b57ddfd --- /dev/null +++ b/src/libsync/transmissionchecksumvalidator.cpp @@ -0,0 +1,151 @@ +/* + * 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() const +{ + QString checksumType = _checksumType; + if( checksumType.isEmpty() ) { + ConfigFile cfg; + checksumType = cfg.transmissionChecksum(); + } + + return checksumType; +} + +void TransmissionChecksumValidator::uploadValidation() +{ + const QString csType = checksumType(); + + if( csType.isEmpty() ) { + // if there is no checksum defined, continue to upload + emit validated(QByteArray()); + } else { + // Calculate the checksum in a different thread first. + + connect( &_watcher, SIGNAL(finished()), + this, SLOT(slotUploadChecksumCalculated())); + if( csType == checkSumMD5C ) { + _checksumHeader = checkSumMD5C; + _checksumHeader += ":"; + _watcher.setFuture(QtConcurrent::run(FileSystem::calcMd5, _filePath)); + + } else if( csType == checkSumSHA1C ) { + _checksumHeader = checkSumSHA1C; + _checksumHeader += ":"; + _watcher.setFuture(QtConcurrent::run( FileSystem::calcSha1, _filePath)); + } +#ifdef ZLIB_FOUND + else if( csType == checkSumAdlerC) { + _checksumHeader = checkSumAdlerC; + _checksumHeader += ":"; + _watcher.setFuture(QtConcurrent::run(FileSystem::calcAdler32, _filePath)); + } +#endif + else { + // for an unknown checksum, continue to upload + emit validated(QByteArray()); + } + } +} + +void TransmissionChecksumValidator::slotUploadChecksumCalculated( ) +{ + QByteArray checksum = _watcher.future().result(); + + if( !checksum.isEmpty() ) { + checksum.prepend( _checksumHeader ); + } + + emit validated(checksum); +} + + +void TransmissionChecksumValidator::downloadValidation( const QByteArray& checksumHeader ) +{ + // if the incoming header is empty, there was no checksum header, and + // no validation can happen. Just continue. + const QString csType = checksumType(); + + // for empty checksum type, everything is valid. + if( csType.isEmpty() ) { + emit validated(QByteArray()); + return; + } + + int indx = checksumHeader.indexOf(':'); + if( indx < 0 ) { + qDebug() << "Checksum header malformed:" << checksumHeader; + emit validationFailed(tr("The checksum header is malformed.")); // show must go on - even not validated. + return; + } + + 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::calcMd5, _filePath)); + } else if( type == checkSumSHA1C ) { + _watcher.setFuture(QtConcurrent::run(FileSystem::calcSha1, _filePath)); + } +#ifdef ZLIB_FOUND + else if( type == checkSumAdlerUpperC ) { + _watcher.setFuture(QtConcurrent::run(FileSystem::calcAdler32, _filePath)); + } +#endif + else { + qDebug() << "Unknown checksum type" << type; + emit validationFailed(tr("The checksum header is malformed.")); + return; + } +} + +void TransmissionChecksumValidator::slotDownloadChecksumCalculated() +{ + const QByteArray hash = _watcher.future().result(); + + if( hash != _expectedHash ) { + emit validationFailed(tr("The downloaded file does not match the checksum, it will be resumed.")); + } else { + // qDebug() << "Checksum checked and matching: " << _expectedHash; + emit validated(hash); + } +} + + +} diff --git a/src/libsync/transmissionchecksumvalidator.h b/src/libsync/transmissionchecksumvalidator.h new file mode 100644 index 000000000..24a289d7e --- /dev/null +++ b/src/libsync/transmissionchecksumvalidator.h @@ -0,0 +1,74 @@ +/* + * 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 TransmissionChecksumValidator : public QObject +{ + Q_OBJECT +public: + explicit TransmissionChecksumValidator(const QString& filePath, QObject *parent = 0); + + /** + * method to prepare a checksum for transmission and save it to the _checksum + * member of the SyncFileItem *item. + * The kind of requested checksum is taken from config. No need to set from outside. + * + * In any case of processing (checksum set, no checksum required and also unusual error) + * the object will emit the signal validated(). The item->_checksum is than either + * set to a proper value or empty. + */ + void uploadValidation(); + + /** + * method to verify the checksum coming with requests in a checksum header. The required + * checksum method is read from config. + * + * If no checksum is there, or if a correct checksum is there, the signal validated() + * will be emitted. In case of any kind of error, the signal validationFailed() will + * be emitted. + */ + void downloadValidation( const QByteArray& checksumHeader ); + + // This is only used in test cases (by now). This class reads the required + // test case from the config file. + void setChecksumType(const QByteArray &type ); + QString checksumType() const; + +signals: + void validated(const QByteArray& checksum); + void validationFailed( const QString& errMsg ); + +private slots: + void slotUploadChecksumCalculated(); + void slotDownloadChecksumCalculated(); + +private: + QByteArray _checksumType; + QByteArray _expectedHash; + QByteArray _checksumHeader; + + QString _filePath; + + // watcher for the checksum calculation thread + QFutureWatcher _watcher; +}; + +} diff --git a/src/libsync/utility.cpp b/src/libsync/utility.cpp index ce9aa2e78..42da7d6c3 100644 --- a/src/libsync/utility.cpp +++ b/src/libsync/utility.cpp @@ -15,6 +15,7 @@ #include "utility.h" #include "version.h" +#include "theme.h" // Note: This file must compile without QtGui #include @@ -154,10 +155,19 @@ QString Utility::platform() QByteArray Utility::userAgentString() { - return QString::fromLatin1("Mozilla/5.0 (%1) mirall/%2") + QString re = QString::fromLatin1("Mozilla/5.0 (%1) mirall/%2") .arg(Utility::platform()) - .arg(QLatin1String(MIRALL_STRINGIFY(MIRALL_VERSION))) - .toLatin1(); + .arg(QLatin1String(MIRALL_STRINGIFY(MIRALL_VERSION))); + + const QString appName = Theme::instance()->appName(); + + // this constant "ownCloud" is defined in the default OEM theming + // that is used for the standard client. If it is changed there, + // it needs to be adjusted here. + if( appName != QLatin1String("ownCloud") ) { + re += QString(" (%1)").arg(appName); + } + return re.toLatin1(); } bool Utility::hasLaunchOnStartup(const QString &appName) @@ -401,10 +411,12 @@ void Utility::StopWatch::start() _timer.start(); } -void Utility::StopWatch::stop() +quint64 Utility::StopWatch::stop() { addLapTime(QLatin1String(STOPWATCH_END_TAG)); + quint64 duration = _timer.elapsed(); _timer.invalidate(); + return duration; } void Utility::StopWatch::reset() diff --git a/src/libsync/utility.h b/src/libsync/utility.h index a9e8bb748..56c241da5 100644 --- a/src/libsync/utility.h +++ b/src/libsync/utility.h @@ -105,7 +105,7 @@ namespace Utility QElapsedTimer _timer; public: void start(); - void stop(); + quint64 stop(); quint64 addLapTime( const QString& lapName ); void reset(); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f1d44061b..ef64f5d2d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -33,4 +33,6 @@ owncloud_add_test(SyncFileItem "") owncloud_add_test(ConcatUrl "") owncloud_add_test(XmlParse "") +owncloud_add_test(FileSystem "") +owncloud_add_test(TransChecksumValidator "") diff --git a/test/testfilesystem.h b/test/testfilesystem.h new file mode 100644 index 000000000..e824edc0f --- /dev/null +++ b/test/testfilesystem.h @@ -0,0 +1,93 @@ +/* + 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. +*/ + +#ifndef MIRALL_TESTFILESYSTEM_H +#define MIRALL_TESTFILESYSTEM_H + +#include +#include + +#include "filesystem.h" +#include "utility.h" + +using namespace OCC::Utility; +using namespace OCC::FileSystem; + +class TestFileSystem : public QObject +{ + Q_OBJECT + + QString _root; + + + QByteArray shellSum( const QByteArray& cmd, const QString& file ) + { + QProcess md5; + QStringList args; + args.append(file); + md5.start(cmd, args); + QByteArray sumShell; + qDebug() << "File: "<< file; + + if( md5.waitForFinished() ) { + + sumShell = md5.readAll(); + sumShell = sumShell.left( sumShell.indexOf(' ')); + } + return sumShell; + } + +private slots: + void initTestCase() { + qsrand(QTime::currentTime().msec()); + + QString subdir("test_"+QString::number(qrand())); + _root = QDir::tempPath() + "/" + subdir; + + QDir dir("/tmp"); + dir.mkdir(subdir); + qDebug() << "creating test directory " << _root; + } + + void cleanupTestCase() + { + if( !_root.isEmpty() ) + system(QString("rm -rf "+_root).toUtf8()); + } + + void testMd5Calc() + { + QString file( _root+"/file_a.bin"); + writeRandomFile(file); + QFileInfo fi(file); + QVERIFY(fi.exists()); + QByteArray sum = calcMd5(file); + + QByteArray sSum = shellSum("/usr/bin/md5sum", file); + qDebug() << "calulated" << sum << "versus md5sum:"<< sSum; + QVERIFY(!sSum.isEmpty()); + QVERIFY(!sum.isEmpty()); + QVERIFY(sSum == sum ); + } + + void testSha1Calc() + { + QString file( _root+"/file_b.bin"); + writeRandomFile(file); + QFileInfo fi(file); + QVERIFY(fi.exists()); + QByteArray sum = calcSha1(file); + + QByteArray sSum = shellSum("/usr/bin/sha1sum", file); + qDebug() << "calulated" << sum << "versus sha1sum:"<< sSum; + QVERIFY(!sSum.isEmpty()); + QVERIFY(!sum.isEmpty()); + QVERIFY(sSum == sum ); + } + +}; + +#endif diff --git a/test/testtranschecksumvalidator.h b/test/testtranschecksumvalidator.h new file mode 100644 index 000000000..5f0ac77b0 --- /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 "utility.h" +#include "filesystem.h" +#include "propagatorjobs.h" + +using namespace OCC; + + class TestTransChecksumValidator : public QObject + { + Q_OBJECT + + private: + QString _root; + QString _testfile; + QString _expectedError; + QEventLoop _loop; + QByteArray _expected; + bool _successDown; + bool _errorSeen; + + void processAndWait() { + _loop.processEvents(); + Utility::usleep(200000); + _loop.processEvents(); + } + + public slots: + + void slotUpValidated(const QByteArray& checksum) { + qDebug() << "Checksum: " << checksum; + QVERIFY(_expected == 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); + + } + + void testUploadChecksummingAdler() { + + TransmissionChecksumValidator *vali = new TransmissionChecksumValidator(_testfile, this); + vali->setChecksumType("Adler32"); + + connect(vali, SIGNAL(validated(QByteArray)), this, SLOT(slotUpValidated(QByteArray))); + + QString testfile = _testfile; + _expected = "Adler32:"+FileSystem::calcAdler32( testfile ); + qDebug() << "XX Expected Checksum: " << _expected; + vali->uploadValidation(); + + usleep(5000); + + _loop.processEvents(); + delete vali; + } + + void testUploadChecksummingMd5() { + + TransmissionChecksumValidator *vali = new TransmissionChecksumValidator(_testfile, this); + vali->setChecksumType( OCC::checkSumMD5C ); + connect(vali, SIGNAL(validated(QByteArray)), this, SLOT(slotUpValidated(QByteArray))); + + _expected = checkSumMD5C; + _expected.append(":"+FileSystem::calcMd5( _testfile )); + vali->uploadValidation(); + + usleep(2000); + + _loop.processEvents(); + delete vali; + } + + void testUploadChecksummingSha1() { + + TransmissionChecksumValidator *vali = new TransmissionChecksumValidator(_testfile, this); + vali->setChecksumType( OCC::checkSumSHA1C ); + connect(vali, SIGNAL(validated(QByteArray)), this, SLOT(slotUpValidated(QByteArray))); + + _expected = checkSumSHA1C; + _expected.append(":"+FileSystem::calcSha1( _testfile )); + + vali->uploadValidation(); + + usleep(2000); + + _loop.processEvents(); + delete vali; + } + + void testDownloadChecksummingAdler() { + + QByteArray adler = checkSumAdlerC; + adler.append(":"); + adler.append(FileSystem::calcAdler32( _testfile )); + _successDown = false; + + TransmissionChecksumValidator *vali = new TransmissionChecksumValidator(_testfile, this); + vali->setChecksumType("Adler32"); + connect(vali, SIGNAL(validated(QByteArray)), this, SLOT(slotDownValidated())); + connect(vali, SIGNAL(validationFailed(QString)), this, SLOT(slotDownError(QString))); + vali->downloadValidation(adler); + + usleep(2000); + + _loop.processEvents(); + QVERIFY(_successDown); + + _expectedError = QLatin1String("The downloaded file does not match the checksum, it will be resumed."); + _errorSeen = false; + vali->downloadValidation("Adler32:543345"); + usleep(2000); + _loop.processEvents(); + QVERIFY(_errorSeen); + + _expectedError = QLatin1String("The checksum header is malformed."); + _errorSeen = false; + vali->downloadValidation("Klaas32:543345"); + usleep(2000); + _loop.processEvents(); + QVERIFY(_errorSeen); + + delete vali; + } + + + void cleanupTestCase() { + } +};