mirror of
https://github.com/nextcloud/desktop.git
synced 2024-10-23 12:55:44 +03:00
Checksums: Work on QIODevice*s
Needed for cfapi where we want to feed data through a custom device which retrieves data from the windows api.
This commit is contained in:
parent
a6614c18f1
commit
22e08cf6ad
4 changed files with 140 additions and 52 deletions
|
@ -89,45 +89,38 @@ Q_LOGGING_CATEGORY(lcChecksums, "nextcloud.sync.checksums", QtInfoMsg)
|
|||
|
||||
#define BUFSIZE qint64(500 * 1024) // 500 KiB
|
||||
|
||||
static QByteArray calcCryptoHash( const QString& filename, QCryptographicHash::Algorithm algo )
|
||||
static QByteArray calcCryptoHash(QIODevice *device, QCryptographicHash::Algorithm algo)
|
||||
{
|
||||
QFile file(filename);
|
||||
QByteArray arr;
|
||||
QCryptographicHash crypto( algo );
|
||||
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
if (crypto.addData(&file)) {
|
||||
arr = crypto.result().toHex();
|
||||
}
|
||||
if (crypto.addData(device)) {
|
||||
arr = crypto.result().toHex();
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
QByteArray calcMd5(const QString &filename)
|
||||
QByteArray calcMd5(QIODevice *device)
|
||||
{
|
||||
return calcCryptoHash(filename, QCryptographicHash::Md5);
|
||||
return calcCryptoHash(device, QCryptographicHash::Md5);
|
||||
}
|
||||
|
||||
QByteArray calcSha1(const QString &filename)
|
||||
QByteArray calcSha1(QIODevice *device)
|
||||
{
|
||||
return calcCryptoHash(filename, QCryptographicHash::Sha1);
|
||||
return calcCryptoHash(device, QCryptographicHash::Sha1);
|
||||
}
|
||||
|
||||
#ifdef ZLIB_FOUND
|
||||
QByteArray calcAdler32(const QString &filename)
|
||||
QByteArray calcAdler32(QIODevice *device)
|
||||
{
|
||||
QFile file(filename);
|
||||
const qint64 bufSize = qMin(BUFSIZE, file.size() + 1);
|
||||
QByteArray buf(bufSize, Qt::Uninitialized);
|
||||
QByteArray buf(BUFSIZE, Qt::Uninitialized);
|
||||
|
||||
unsigned int adler = adler32(0L, Z_NULL, 0);
|
||||
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);
|
||||
}
|
||||
qint64 size;
|
||||
while (!device->atEnd()) {
|
||||
size = device->read(buf.data(), BUFSIZE);
|
||||
if (size > 0)
|
||||
adler = adler32(adler, (const Bytef *)buf.data(), size);
|
||||
}
|
||||
|
||||
return QByteArray::number(adler, 16);
|
||||
|
@ -228,15 +221,38 @@ QByteArray ComputeChecksum::checksumType() const
|
|||
void ComputeChecksum::start(const QString &filePath)
|
||||
{
|
||||
qCInfo(lcChecksums) << "Computing" << checksumType() << "checksum of" << filePath << "in a thread";
|
||||
_file = new QFile(filePath, this);
|
||||
if (!_file->open(QIODevice::ReadOnly)) {
|
||||
qCWarning(lcChecksums) << "Could not open file" << filePath << "for reading to compute a checksum" << _file->errorString();
|
||||
emit done(QByteArray(), QByteArray());
|
||||
return;
|
||||
}
|
||||
start(_file);
|
||||
}
|
||||
|
||||
void ComputeChecksum::start(QIODevice *device)
|
||||
{
|
||||
qCInfo(lcChecksums) << "Computing" << checksumType() << "checksum of iodevice in a thread";
|
||||
|
||||
// Calculate the checksum in a different thread first.
|
||||
connect(&_watcher, &QFutureWatcherBase::finished,
|
||||
this, &ComputeChecksum::slotCalculationDone,
|
||||
Qt::UniqueConnection);
|
||||
_watcher.setFuture(QtConcurrent::run(ComputeChecksum::computeNow, filePath, checksumType()));
|
||||
_watcher.setFuture(QtConcurrent::run(ComputeChecksum::computeNow, device, checksumType()));
|
||||
}
|
||||
|
||||
QByteArray ComputeChecksum::computeNow(const QString &filePath, const QByteArray &checksumType)
|
||||
QByteArray ComputeChecksum::computeNowOnFile(const QString &filePath, const QByteArray &checksumType)
|
||||
{
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCWarning(lcChecksums) << "Could not open file" << filePath << "for reading and computing checksum" << file.errorString();
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
return computeNow(&file, checksumType);
|
||||
}
|
||||
|
||||
QByteArray ComputeChecksum::computeNow(QIODevice *device, const QByteArray &checksumType)
|
||||
{
|
||||
if (!checksumComputationEnabled()) {
|
||||
qCWarning(lcChecksums) << "Checksum computation disabled by environment variable";
|
||||
|
@ -244,20 +260,20 @@ QByteArray ComputeChecksum::computeNow(const QString &filePath, const QByteArray
|
|||
}
|
||||
|
||||
if (checksumType == checkSumMD5C) {
|
||||
return calcMd5(filePath);
|
||||
return calcMd5(device);
|
||||
} else if (checksumType == checkSumSHA1C) {
|
||||
return calcSha1(filePath);
|
||||
return calcSha1(device);
|
||||
} else if (checksumType == checkSumSHA2C) {
|
||||
return calcCryptoHash(filePath, QCryptographicHash::Sha256);
|
||||
return calcCryptoHash(device, QCryptographicHash::Sha256);
|
||||
}
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
|
||||
else if (checksumType == checkSumSHA3C) {
|
||||
return calcCryptoHash(filePath, QCryptographicHash::Sha3_256);
|
||||
return calcCryptoHash(device, QCryptographicHash::Sha3_256);
|
||||
}
|
||||
#endif
|
||||
#ifdef ZLIB_FOUND
|
||||
else if (checksumType == checkSumAdlerC) {
|
||||
return calcAdler32(filePath);
|
||||
return calcAdler32(device);
|
||||
}
|
||||
#endif
|
||||
// for an unknown checksum or no checksum, we're done right now
|
||||
|
@ -269,6 +285,10 @@ QByteArray ComputeChecksum::computeNow(const QString &filePath, const QByteArray
|
|||
|
||||
void ComputeChecksum::slotCalculationDone()
|
||||
{
|
||||
// Close the file and delete the instance
|
||||
if (_file)
|
||||
delete _file;
|
||||
|
||||
QByteArray checksum = _watcher.future().result();
|
||||
if (!checksum.isNull()) {
|
||||
emit done(_checksumType, checksum);
|
||||
|
@ -283,25 +303,37 @@ ValidateChecksumHeader::ValidateChecksumHeader(QObject *parent)
|
|||
{
|
||||
}
|
||||
|
||||
void ValidateChecksumHeader::start(const QString &filePath, const QByteArray &checksumHeader)
|
||||
ComputeChecksum *ValidateChecksumHeader::prepareStart(const QByteArray &checksumHeader)
|
||||
{
|
||||
// If the incoming header is empty no validation can happen. Just continue.
|
||||
if (checksumHeader.isEmpty()) {
|
||||
emit validated(QByteArray(), QByteArray());
|
||||
return;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!parseChecksumHeader(checksumHeader, &_expectedChecksumType, &_expectedChecksum)) {
|
||||
qCWarning(lcChecksums) << "Checksum header malformed:" << checksumHeader;
|
||||
emit validationFailed(tr("The checksum header is malformed."));
|
||||
return;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto calculator = new ComputeChecksum(this);
|
||||
calculator->setChecksumType(_expectedChecksumType);
|
||||
connect(calculator, &ComputeChecksum::done,
|
||||
this, &ValidateChecksumHeader::slotChecksumCalculated);
|
||||
calculator->start(filePath);
|
||||
return calculator;
|
||||
}
|
||||
|
||||
void ValidateChecksumHeader::start(const QString &filePath, const QByteArray &checksumHeader)
|
||||
{
|
||||
if (auto calculator = prepareStart(checksumHeader))
|
||||
calculator->start(filePath);
|
||||
}
|
||||
|
||||
void ValidateChecksumHeader::start(QIODevice *device, const QByteArray &checksumHeader)
|
||||
{
|
||||
if (auto calculator = prepareStart(checksumHeader))
|
||||
calculator->start(device);
|
||||
}
|
||||
|
||||
void ValidateChecksumHeader::slotChecksumCalculated(const QByteArray &checksumType,
|
||||
|
@ -327,7 +359,7 @@ QByteArray CSyncChecksumHook::hook(const QByteArray &path, const QByteArray &oth
|
|||
return nullptr;
|
||||
|
||||
qCInfo(lcChecksums) << "Computing" << type << "checksum of" << path << "in the csync hook";
|
||||
QByteArray checksum = ComputeChecksum::computeNow(QString::fromUtf8(path), type);
|
||||
QByteArray checksum = ComputeChecksum::computeNowOnFile(QString::fromUtf8(path), type);
|
||||
if (checksum.isNull()) {
|
||||
qCWarning(lcChecksums) << "Failed to compute checksum" << type << "for" << path;
|
||||
return nullptr;
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
#include <QByteArray>
|
||||
#include <QFutureWatcher>
|
||||
|
||||
class QFile;
|
||||
|
||||
namespace OCC {
|
||||
|
||||
/**
|
||||
|
@ -65,10 +67,10 @@ OCSYNC_EXPORT bool uploadChecksumEnabled();
|
|||
OCSYNC_EXPORT QByteArray contentChecksumType();
|
||||
|
||||
// Exported functions for the tests.
|
||||
QByteArray OCSYNC_EXPORT calcMd5(const QString &fileName);
|
||||
QByteArray OCSYNC_EXPORT calcSha1(const QString &fileName);
|
||||
QByteArray OCSYNC_EXPORT calcMd5(QIODevice *device);
|
||||
QByteArray OCSYNC_EXPORT calcSha1(QIODevice *device);
|
||||
#ifdef ZLIB_FOUND
|
||||
QByteArray OCSYNC_EXPORT calcAdler32(const QString &fileName);
|
||||
QByteArray OCSYNC_EXPORT calcAdler32(QIODevice *device);
|
||||
#endif
|
||||
|
||||
/**
|
||||
|
@ -88,17 +90,34 @@ public:
|
|||
|
||||
QByteArray checksumType() const;
|
||||
|
||||
/**
|
||||
* Computes the checksum for given device.
|
||||
*
|
||||
* done() is emitted when the calculation finishes.
|
||||
*
|
||||
* Does not take ownership of the device.
|
||||
* Does not call open() on the device.
|
||||
*/
|
||||
void start(QIODevice *device);
|
||||
|
||||
/**
|
||||
* Computes the checksum for the given file path.
|
||||
*
|
||||
* done() is emitted when the calculation finishes.
|
||||
*
|
||||
* Convenience wrapper for start(QIODevice*) above.
|
||||
*/
|
||||
void start(const QString &filePath);
|
||||
|
||||
/**
|
||||
* Computes the checksum synchronously.
|
||||
*/
|
||||
static QByteArray computeNow(const QString &filePath, const QByteArray &checksumType);
|
||||
static QByteArray computeNow(QIODevice *device, const QByteArray &checksumType);
|
||||
|
||||
/**
|
||||
* Computes the checksum synchronously on file. Convenience wrapper for computeNow().
|
||||
*/
|
||||
static QByteArray computeNowOnFile(const QString &filePath, const QByteArray &checksumType);
|
||||
|
||||
signals:
|
||||
void done(const QByteArray &checksumType, const QByteArray &checksum);
|
||||
|
@ -109,6 +128,9 @@ private slots:
|
|||
private:
|
||||
QByteArray _checksumType;
|
||||
|
||||
// The convenience wrapper may open a file and must close it too
|
||||
QFile *_file = nullptr;
|
||||
|
||||
// watcher for the checksum calculation thread
|
||||
QFutureWatcher<QByteArray> _watcher;
|
||||
};
|
||||
|
@ -124,11 +146,21 @@ public:
|
|||
explicit ValidateChecksumHeader(QObject *parent = nullptr);
|
||||
|
||||
/**
|
||||
* Check a file's actual checksum against the provided checksumHeader
|
||||
* Check a device's actual checksum against the provided checksumHeader
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Does not take ownership of the device.
|
||||
* Does not call open() on the device.
|
||||
*/
|
||||
void start(QIODevice *device, const QByteArray &checksumHeader);
|
||||
|
||||
/**
|
||||
* Same as above but opening a file by path.
|
||||
*
|
||||
* Convenience function for start(QIODevice*) above
|
||||
*/
|
||||
void start(const QString &filePath, const QByteArray &checksumHeader);
|
||||
|
||||
|
@ -140,6 +172,8 @@ private slots:
|
|||
void slotChecksumCalculated(const QByteArray &checksumType, const QByteArray &checksum);
|
||||
|
||||
private:
|
||||
ComputeChecksum *prepareStart(const QByteArray &checksumHeader);
|
||||
|
||||
QByteArray _expectedChecksumType;
|
||||
QByteArray _expectedChecksum;
|
||||
};
|
||||
|
|
|
@ -313,7 +313,7 @@ static bool computeLocalChecksum(const QByteArray &header, const QString &path,
|
|||
auto type = parseChecksumHeaderType(header);
|
||||
if (!type.isEmpty()) {
|
||||
// TODO: compute async?
|
||||
QByteArray checksum = ComputeChecksum::computeNow(path, type);
|
||||
QByteArray checksum = ComputeChecksum::computeNowOnFile(path, type);
|
||||
if (!checksum.isEmpty()) {
|
||||
item->_checksumHeader = makeChecksumHeader(type, checksum);
|
||||
return true;
|
||||
|
|
|
@ -77,7 +77,11 @@ using namespace OCC::Utility;
|
|||
QVERIFY(writeRandomFile(file));
|
||||
QFileInfo fi(file);
|
||||
QVERIFY(fi.exists());
|
||||
QByteArray sum = calcMd5(file);
|
||||
|
||||
QFile fileDevice(file);
|
||||
fileDevice.open(QIODevice::ReadOnly);
|
||||
QByteArray sum = calcMd5(&fileDevice);
|
||||
fileDevice.close();
|
||||
|
||||
QByteArray sSum = shellSum("md5sum", file);
|
||||
if (sSum.isEmpty())
|
||||
|
@ -93,7 +97,11 @@ using namespace OCC::Utility;
|
|||
writeRandomFile(file);
|
||||
QFileInfo fi(file);
|
||||
QVERIFY(fi.exists());
|
||||
QByteArray sum = calcSha1(file);
|
||||
|
||||
QFile fileDevice(file);
|
||||
fileDevice.open(QIODevice::ReadOnly);
|
||||
QByteArray sum = calcSha1(&fileDevice);
|
||||
fileDevice.close();
|
||||
|
||||
QByteArray sSum = shellSum("sha1sum", file);
|
||||
if (sSum.isEmpty())
|
||||
|
@ -113,7 +121,9 @@ using namespace OCC::Utility;
|
|||
|
||||
connect(vali, SIGNAL(done(QByteArray,QByteArray)), SLOT(slotUpValidated(QByteArray,QByteArray)));
|
||||
|
||||
_expected = calcAdler32( _testfile );
|
||||
auto file = new QFile(_testfile, vali);
|
||||
file->open(QIODevice::ReadOnly);
|
||||
_expected = calcAdler32(file);
|
||||
qDebug() << "XX Expected Checksum: " << _expected;
|
||||
vali->start(_testfile);
|
||||
|
||||
|
@ -132,7 +142,9 @@ using namespace OCC::Utility;
|
|||
vali->setChecksumType(_expectedType);
|
||||
connect(vali, SIGNAL(done(QByteArray,QByteArray)), this, SLOT(slotUpValidated(QByteArray,QByteArray)));
|
||||
|
||||
_expected = calcMd5( _testfile );
|
||||
auto file = new QFile(_testfile, vali);
|
||||
file->open(QIODevice::ReadOnly);
|
||||
_expected = calcMd5(file);
|
||||
vali->start(_testfile);
|
||||
|
||||
QEventLoop loop;
|
||||
|
@ -149,7 +161,9 @@ using namespace OCC::Utility;
|
|||
vali->setChecksumType(_expectedType);
|
||||
connect(vali, SIGNAL(done(QByteArray,QByteArray)), this, SLOT(slotUpValidated(QByteArray,QByteArray)));
|
||||
|
||||
_expected = calcSha1( _testfile );
|
||||
auto file = new QFile(_testfile, vali);
|
||||
file->open(QIODevice::ReadOnly);
|
||||
_expected = calcSha1(file);
|
||||
|
||||
vali->start(_testfile);
|
||||
|
||||
|
@ -164,26 +178,34 @@ using namespace OCC::Utility;
|
|||
#ifndef ZLIB_FOUND
|
||||
QSKIP("ZLIB not found.", SkipSingle);
|
||||
#else
|
||||
QByteArray adler = checkSumAdlerC;
|
||||
adler.append(":");
|
||||
adler.append(calcAdler32( _testfile ));
|
||||
_successDown = false;
|
||||
|
||||
auto *vali = new ValidateChecksumHeader(this);
|
||||
connect(vali, SIGNAL(validated(QByteArray,QByteArray)), this, SLOT(slotDownValidated()));
|
||||
connect(vali, SIGNAL(validationFailed(QString)), this, SLOT(slotDownError(QString)));
|
||||
vali->start(_testfile, adler);
|
||||
|
||||
auto file = new QFile(_testfile, vali);
|
||||
file->open(QIODevice::ReadOnly);
|
||||
_expected = calcAdler32(file);
|
||||
|
||||
QByteArray adler = checkSumAdlerC;
|
||||
adler.append(":");
|
||||
adler.append(_expected);
|
||||
|
||||
file->seek(0);
|
||||
_successDown = false;
|
||||
vali->start(file, adler);
|
||||
|
||||
QTRY_VERIFY(_successDown);
|
||||
|
||||
_expectedError = QLatin1String("The downloaded file does not match the checksum, it will be resumed.");
|
||||
_errorSeen = false;
|
||||
vali->start(_testfile, "Adler32:543345");
|
||||
file->seek(0);
|
||||
vali->start(file, "Adler32:543345");
|
||||
QTRY_VERIFY(_errorSeen);
|
||||
|
||||
_expectedError = QLatin1String("The checksum header contained an unknown checksum type 'Klaas32'");
|
||||
_errorSeen = false;
|
||||
vali->start(_testfile, "Klaas32:543345");
|
||||
file->seek(0);
|
||||
vali->start(file, "Klaas32:543345");
|
||||
QTRY_VERIFY(_errorSeen);
|
||||
|
||||
delete vali;
|
||||
|
|
Loading…
Reference in a new issue