mirror of
https://github.com/nextcloud/desktop.git
synced 2024-10-23 12:55:44 +03:00
Ask server to recalculate checksum on validatin failure.
Signed-off-by: alex-z <blackslayer4@gmail.com>
This commit is contained in:
parent
fbe35538b6
commit
b7be10f712
10 changed files with 130 additions and 14 deletions
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 )
|
||||
|
|
Loading…
Reference in a new issue