networkjobs.cpp
owncloudpropagator.cpp
nextcloudtheme.cpp
+ abstractpropagateremotedeleteencrypted.cpp
+ deletejob.cpp
progressdispatcher.cpp
propagatorjobs.cpp
propagatedownload.cpp
propagateuploadng.cpp
propagateremotedelete.cpp
propagateremotedeleteencrypted.cpp
+ propagateremotedeleteencryptedrootfolder.cpp
propagateremotemove.cpp
propagateremotemkdir.cpp
propagateuploadencrypted.cpp
--- /dev/null
+/*
+ * Copyright (C) by Oleksandr Zolotov <alex@nextcloud.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 <QFileInfo>
+#include <QLoggingCategory>
+
+#include "abstractpropagateremotedeleteencrypted.h"
+#include "account.h"
+#include "clientsideencryptionjobs.h"
+#include "deletejob.h"
+#include "owncloudpropagator.h"
+
+Q_LOGGING_CATEGORY(ABSTRACT_PROPAGATE_REMOVE_ENCRYPTED, "nextcloud.sync.propagator.remove.encrypted")
+
+namespace OCC {
+
+AbstractPropagateRemoteDeleteEncrypted::AbstractPropagateRemoteDeleteEncrypted(OwncloudPropagator *propagator, SyncFileItemPtr item, QObject *parent)
+ : QObject(parent)
+ , _propagator(propagator)
+ , _item(item)
+{}
+
+QNetworkReply::NetworkError AbstractPropagateRemoteDeleteEncrypted::networkError() const
+{
+ return _networkError;
+}
+
+QString AbstractPropagateRemoteDeleteEncrypted::errorString() const
+{
+ return _errorString;
+}
+
+void AbstractPropagateRemoteDeleteEncrypted::storeFirstError(QNetworkReply::NetworkError err)
+{
+ if (_networkError == QNetworkReply::NetworkError::NoError) {
+ _networkError = err;
+ }
+}
+
+void AbstractPropagateRemoteDeleteEncrypted::storeFirstErrorString(const QString &errString)
+{
+ if (_errorString.isEmpty()) {
+ _errorString = errString;
+ }
+}
+
+void AbstractPropagateRemoteDeleteEncrypted::startLsColJob(const QString &path)
+{
+ qCDebug(ABSTRACT_PROPAGATE_REMOVE_ENCRYPTED) << "Folder is encrypted, let's get the Id from it.";
+ auto job = new LsColJob(_propagator->account(), path, this);
+ job->setProperties({"resourcetype", "http://owncloud.org/ns:fileid"});
+ connect(job, &LsColJob::directoryListingSubfolders, this, &AbstractPropagateRemoteDeleteEncrypted::slotFolderEncryptedIdReceived);
+ connect(job, &LsColJob::finishedWithError, this, &AbstractPropagateRemoteDeleteEncrypted::taskFailed);
+ job->start();
+}
+
+void AbstractPropagateRemoteDeleteEncrypted::slotFolderEncryptedIdReceived(const QStringList &list)
+{
+ qCDebug(ABSTRACT_PROPAGATE_REMOVE_ENCRYPTED) << "Received id of folder, trying to lock it so we can prepare the metadata";
+ auto job = qobject_cast<LsColJob *>(sender());
+ const ExtraFolderInfo folderInfo = job->_folderInfos.value(list.first());
+ slotTryLock(folderInfo.fileId);
+}
+
+void AbstractPropagateRemoteDeleteEncrypted::slotTryLock(const QByteArray &folderId)
+{
+ auto lockJob = new LockEncryptFolderApiJob(_propagator->account(), folderId, this);
+ connect(lockJob, &LockEncryptFolderApiJob::success, this, &AbstractPropagateRemoteDeleteEncrypted::slotFolderLockedSuccessfully);
+ connect(lockJob, &LockEncryptFolderApiJob::error, this, &AbstractPropagateRemoteDeleteEncrypted::taskFailed);
+ lockJob->start();
+}
+
+void AbstractPropagateRemoteDeleteEncrypted::slotFolderLockedSuccessfully(const QByteArray &folderId, const QByteArray &token)
+{
+ qCDebug(ABSTRACT_PROPAGATE_REMOVE_ENCRYPTED) << "Folder id" << folderId << "Locked Successfully for Upload, Fetching Metadata";
+ _folderLocked = true;
+ _folderToken = token;
+ _folderId = folderId;
+
+ auto job = new GetMetadataApiJob(_propagator->account(), _folderId);
+ connect(job, &GetMetadataApiJob::jsonReceived, this, &AbstractPropagateRemoteDeleteEncrypted::slotFolderEncryptedMetadataReceived);
+ connect(job, &GetMetadataApiJob::error, this, &AbstractPropagateRemoteDeleteEncrypted::taskFailed);
+ job->start();
+}
+
+void AbstractPropagateRemoteDeleteEncrypted::slotFolderUnLockedSuccessfully(const QByteArray &folderId)
+{
+ Q_UNUSED(folderId);
+ qCDebug(ABSTRACT_PROPAGATE_REMOVE_ENCRYPTED) << "Folder id" << folderId << "successfully unlocked";
+ _folderLocked = false;
+ _folderToken = "";
+}
+
+void AbstractPropagateRemoteDeleteEncrypted::slotDeleteRemoteItemFinished()
+{
+ auto *deleteJob = qobject_cast<DeleteJob *>(QObject::sender());
+
+ Q_ASSERT(deleteJob);
+
+ if (!deleteJob) {
+ qCCritical(ABSTRACT_PROPAGATE_REMOVE_ENCRYPTED) << "Sender is not a DeleteJob instance.";
+ taskFailed();
+ return;
+ }
+
+ const auto err = deleteJob->reply()->error();
+
+ _item->_httpErrorCode = deleteJob->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+ _item->_responseTimeStamp = deleteJob->responseTimestamp();
+ _item->_requestId = deleteJob->requestId();
+
+ if (err != QNetworkReply::NoError && err != QNetworkReply::ContentNotFoundError) {
+ storeFirstErrorString(deleteJob->errorString());
+ storeFirstError(err);
+
+ taskFailed();
+ return;
+ }
+
+ // A 404 reply is also considered a success here: We want to make sure
+ // a file is gone from the server. It not being there in the first place
+ // is ok. This will happen for files that are in the DB but not on
+ // the server or the local file system.
+ if (_item->_httpErrorCode != 204 && _item->_httpErrorCode != 404) {
+ // Normally we expect "204 No Content"
+ // If it is not the case, it might be because of a proxy or gateway intercepting the request, so we must
+ // throw an error.
+ storeFirstErrorString(tr("Wrong HTTP code returned by server. Expected 204, but received \"%1 %2\".")
+ .arg(_item->_httpErrorCode)
+ .arg(deleteJob->reply()->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString()));
+
+ taskFailed();
+ return;
+ }
+
+ _propagator->_journal->deleteFileRecord(_item->_originalFile, _item->isDirectory());
+ _propagator->_journal->commit("Remote Remove");
+
+ unlockFolder();
+}
+
+void AbstractPropagateRemoteDeleteEncrypted::deleteRemoteItem(const QString &filename)
+{
+ qCInfo(ABSTRACT_PROPAGATE_REMOVE_ENCRYPTED) << "Deleting nested encrypted item" << filename;
+
+ auto deleteJob = new DeleteJob(_propagator->account(), _propagator->fullRemotePath(filename), this);
+ deleteJob->setFolderToken(_folderToken);
+
+ connect(deleteJob, &DeleteJob::finishedSignal, this, &AbstractPropagateRemoteDeleteEncrypted::slotDeleteRemoteItemFinished);
+
+ deleteJob->start();
+}
+
+void AbstractPropagateRemoteDeleteEncrypted::unlockFolder()
+{
+ if (!_folderLocked) {
+ emit finished(true);
+ return;
+ }
+
+ qCDebug(ABSTRACT_PROPAGATE_REMOVE_ENCRYPTED) << "Unlocking folder" << _folderId;
+ auto unlockJob = new UnlockEncryptFolderApiJob(_propagator->account(), _folderId, _folderToken, this);
+
+ connect(unlockJob, &UnlockEncryptFolderApiJob::success, this, &AbstractPropagateRemoteDeleteEncrypted::slotFolderUnLockedSuccessfully);
+ connect(unlockJob, &UnlockEncryptFolderApiJob::error, this, [this] (const QByteArray& fileId, int httpReturnCode) {
+ Q_UNUSED(fileId);
+ _folderLocked = false;
+ _folderToken = "";
+ _item->_httpErrorCode = httpReturnCode;
+ _errorString = tr("\"%1 Failed to unlock encrypted folder %2\".")
+ .arg(httpReturnCode)
+ .arg(QString::fromUtf8(fileId));
+ _item->_errorString =_errorString;
+ taskFailed();
+ });
+ unlockJob->start();
+}
+
+void AbstractPropagateRemoteDeleteEncrypted::taskFailed()
+{
+ qCDebug(ABSTRACT_PROPAGATE_REMOVE_ENCRYPTED) << "Task failed for job" << sender();
+ _isTaskFailed = true;
+ if (_folderLocked) {
+ unlockFolder();
+ } else {
+ emit finished(false);
+ }
+}
+
+} // namespace OCC
--- /dev/null
+/*
+ * Copyright (C) by Oleksandr Zolotov <alex@nextcloud.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 <QString>
+#include <QNetworkReply>
+
+#include "syncfileitem.h"
+
+namespace OCC {
+
+class OwncloudPropagator;
+/**
+ * @brief The AbstractPropagateRemoteDeleteEncrypted class is the base class for Propagate Remote Delete Encrypted jobs
+ * @ingroup libsync
+ */
+class AbstractPropagateRemoteDeleteEncrypted : public QObject
+{
+ Q_OBJECT
+public:
+ AbstractPropagateRemoteDeleteEncrypted(OwncloudPropagator *propagator, SyncFileItemPtr item, QObject *parent);
+ virtual ~AbstractPropagateRemoteDeleteEncrypted() = default;
+
+ QNetworkReply::NetworkError networkError() const;
+ QString errorString() const;
+
+ virtual void start() = 0;
+
+signals:
+ void finished(bool success);
+
+protected:
+ void storeFirstError(QNetworkReply::NetworkError err);
+ void storeFirstErrorString(const QString &errString);
+
+ void startLsColJob(const QString &path);
+ void slotFolderEncryptedIdReceived(const QStringList &list);
+ void slotTryLock(const QByteArray &folderId);
+ void slotFolderLockedSuccessfully(const QByteArray &folderId, const QByteArray &token);
+ virtual void slotFolderUnLockedSuccessfully(const QByteArray &folderId);
+ virtual void slotFolderEncryptedMetadataReceived(const QJsonDocument &json, int statusCode) = 0;
+ void slotDeleteRemoteItemFinished();
+
+ void deleteRemoteItem(const QString &filename);
+ void unlockFolder();
+ void taskFailed();
+
+protected:
+ OwncloudPropagator *_propagator = nullptr;
+ SyncFileItemPtr _item;
+ QByteArray _folderToken;
+ QByteArray _folderId;
+ bool _folderLocked = false;
+ bool _isTaskFailed = false;
+ QNetworkReply::NetworkError _networkError = QNetworkReply::NoError;
+ QString _errorString;
+};
+
+}
}
}
+void FolderMetadata::removeAllEncryptedFiles()
+{
+ _files.clear();
+}
+
QVector<EncryptedFile> FolderMetadata::files() const {
return _files;
}
QByteArray encryptedMetadata();
void addEncryptedFile(const EncryptedFile& f);
void removeEncryptedFile(const EncryptedFile& f);
+ void removeAllEncryptedFiles();
QVector<EncryptedFile> files() const;
return true;
}
-SetEncryptionFlagApiJob::SetEncryptionFlagApiJob(const AccountPtr& account, const QByteArray& fileId, QObject* parent)
-: AbstractNetworkJob(account, baseUrl() + QStringLiteral("encrypted/") + fileId, parent), _fileId(fileId)
+SetEncryptionFlagApiJob::SetEncryptionFlagApiJob(const AccountPtr& account, const QByteArray& fileId, FlagAction flagAction, QObject* parent)
+: AbstractNetworkJob(account, baseUrl() + QStringLiteral("encrypted/") + fileId, parent), _fileId(fileId), _flagAction(flagAction)
{
}
req.setRawHeader("OCS-APIREQUEST", "true");
QUrl url = Utility::concatUrlPath(account()->url(), path());
- qCInfo(lcCseJob()) << "marking the file with id" << _fileId << "as encrypted";
- sendRequest("PUT", url, req);
+ qCInfo(lcCseJob()) << "marking the file with id" << _fileId << "as" << (_flagAction == Set ? "encrypted" : "non-encrypted") << ".";
+
+ sendRequest(_flagAction == Set ? "PUT" : "DELETE", url, req);
+
AbstractNetworkJob::start();
}
{
Q_OBJECT
public:
- explicit SetEncryptionFlagApiJob(const AccountPtr &account, const QByteArray& fileId, QObject *parent = nullptr);
+ enum FlagAction {
+ Clear = 0,
+ Set = 1
+ };
+
+ explicit SetEncryptionFlagApiJob(const AccountPtr &account, const QByteArray &fileId, FlagAction flagAction = Set, QObject *parent = nullptr);
public slots:
void start() override;
bool finished() override;
signals:
- void success(const QByteArray fileId);
- void error(const QByteArray fileId, int httpReturnCode);
+ void success(const QByteArray &fileId);
+ void error(const QByteArray &fileId, int httpReturnCode);
private:
QByteArray _fileId;
+ FlagAction _flagAction = Set;
};
class OWNCLOUDSYNC_EXPORT LockEncryptFolderApiJob : public AbstractNetworkJob
--- /dev/null
+/*
+ * Copyright (C) by Olivier Goffart <ogoffart@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 "deletejob.h"
+#include "account.h"
+#include <QLoggingCategory>
+
+namespace OCC {
+
+Q_LOGGING_CATEGORY(lcDeleteJob, "nextcloud.sync.networkjob.delete", QtInfoMsg)
+
+DeleteJob::DeleteJob(AccountPtr account, const QString &path, QObject *parent)
+ : AbstractNetworkJob(account, path, parent)
+{
+}
+
+DeleteJob::DeleteJob(AccountPtr account, const QUrl &url, QObject *parent)
+ : AbstractNetworkJob(account, QString(), parent)
+ , _url(url)
+{
+}
+
+void DeleteJob::start()
+{
+ QNetworkRequest req;
+ if (!_folderToken.isEmpty()) {
+ req.setRawHeader("e2e-token", _folderToken);
+ }
+
+ if (_url.isValid()) {
+ sendRequest("DELETE", _url, req);
+ } else {
+ sendRequest("DELETE", makeDavUrl(path()), req);
+ }
+
+ if (reply()->error() != QNetworkReply::NoError) {
+ qCWarning(lcDeleteJob) << " Network error: " << reply()->errorString();
+ }
+ AbstractNetworkJob::start();
+}
+
+bool DeleteJob::finished()
+{
+ qCInfo(lcDeleteJob) << "DELETE of" << reply()->request().url() << "FINISHED WITH STATUS"
+ << replyStatusString();
+
+ emit finishedSignal();
+ return true;
+}
+
+QByteArray DeleteJob::folderToken() const
+{
+ return _folderToken;
+}
+
+void DeleteJob::setFolderToken(const QByteArray &folderToken)
+{
+ _folderToken = folderToken;
+}
+
+} // namespace OCC
--- /dev/null
+/*
+ * Copyright (C) by Olivier Goffart <ogoffart@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 "accountfwd.h"
+#include "networkjobs.h"
+
+namespace OCC {
+
+/**
+ * @brief The DeleteJob class
+ * @ingroup libsync
+ */
+class DeleteJob : public AbstractNetworkJob
+{
+ Q_OBJECT
+public:
+ explicit DeleteJob(AccountPtr account, const QString &path, QObject *parent = nullptr);
+ explicit DeleteJob(AccountPtr account, const QUrl &url, QObject *parent = nullptr);
+
+ void start() override;
+ bool finished() override;
+
+ QByteArray folderToken() const;
+ void setFolderToken(const QByteArray &folderToken);
+
+signals:
+ void finishedSignal();
+
+private:
+ QUrl _url; // Only used if the constructor taking a url is taken.
+ QByteArray _folderToken;
+};
+}
void EncryptFolderJob::start()
{
- auto job = new OCC::SetEncryptionFlagApiJob(_account, _fileId, this);
+ auto job = new OCC::SetEncryptionFlagApiJob(_account, _fileId, OCC::SetEncryptionFlagApiJob::Set, this);
connect(job, &OCC::SetEncryptionFlagApiJob::success, this, &EncryptFolderJob::slotEncryptionFlagSuccess);
connect(job, &OCC::SetEncryptionFlagApiJob::error, this, &EncryptFolderJob::slotEncryptionFlagError);
job->start();
#include "propagateremotedelete.h"
#include "propagateremotedeleteencrypted.h"
+#include "propagateremotedeleteencryptedrootfolder.h"
#include "owncloudpropagator_p.h"
#include "account.h"
+#include "deletejob.h"
#include "common/asserts.h"
#include <QLoggingCategory>
namespace OCC {
-Q_LOGGING_CATEGORY(lcDeleteJob, "nextcloud.sync.networkjob.delete", QtInfoMsg)
Q_LOGGING_CATEGORY(lcPropagateRemoteDelete, "nextcloud.sync.propagator.remotedelete", QtInfoMsg)
-DeleteJob::DeleteJob(AccountPtr account, const QString &path, QObject *parent)
- : AbstractNetworkJob(account, path, parent)
-{
-}
-
-DeleteJob::DeleteJob(AccountPtr account, const QUrl &url, QObject *parent)
- : AbstractNetworkJob(account, QString(), parent)
- , _url(url)
-{
-}
-
-void DeleteJob::start()
-{
- QNetworkRequest req;
- if (!_folderToken.isEmpty()) {
- req.setRawHeader("e2e-token", _folderToken);
- }
-
- if (_url.isValid()) {
- sendRequest("DELETE", _url, req);
- } else {
- sendRequest("DELETE", makeDavUrl(path()), req);
- }
-
- if (reply()->error() != QNetworkReply::NoError) {
- qCWarning(lcDeleteJob) << " Network error: " << reply()->errorString();
- }
- AbstractNetworkJob::start();
-}
-
-bool DeleteJob::finished()
-{
- qCInfo(lcDeleteJob) << "DELETE of" << reply()->request().url() << "FINISHED WITH STATUS"
- << replyStatusString();
-
- emit finishedSignal();
- return true;
-}
-
-QByteArray DeleteJob::folderToken() const
-{
- return _folderToken;
-}
-
-void DeleteJob::setFolderToken(const QByteArray &folderToken)
-{
- _folderToken = folderToken;
-}
-
PropagatorJob::JobParallelism PropagateRemoteDelete::parallelism()
{
return _item->_encryptedFileName.isEmpty() ? FullParallelism : WaitForFinished;
if (propagator()->_abortRequested)
return;
- if (!_item->_encryptedFileName.isEmpty()) {
- _deleteEncryptedHelper = new PropagateRemoteDeleteEncrypted(propagator(), _item, this);
- connect(_deleteEncryptedHelper, &PropagateRemoteDeleteEncrypted::finished, this, [this] (bool success) {
- Q_UNUSED(success) // Should we skip file deletion in case of failure?
- createDeleteJob(_item->_encryptedFileName);
+ if (!_item->_encryptedFileName.isEmpty() || _item->_isEncrypted) {
+ if (!_item->_encryptedFileName.isEmpty()) {
+ _deleteEncryptedHelper = new PropagateRemoteDeleteEncrypted(propagator(), _item, this);
+ } else {
+ _deleteEncryptedHelper = new PropagateRemoteDeleteEncryptedRootFolder(propagator(), _item, this);
+ }
+ connect(_deleteEncryptedHelper, &AbstractPropagateRemoteDeleteEncrypted::finished, this, [this] (bool success) {
+ if (!success) {
+ SyncFileItem::Status status = SyncFileItem::NormalError;
+ if (_deleteEncryptedHelper->networkError() != QNetworkReply::NoError && _deleteEncryptedHelper->networkError() != QNetworkReply::ContentNotFoundError) {
+ status = classifyError(_deleteEncryptedHelper->networkError(), _item->_httpErrorCode, &propagator()->_anotherSyncNeeded);
+ }
+ done(status, _deleteEncryptedHelper->errorString());
+ } else {
+ done(SyncFileItem::Success);
+ }
});
_deleteEncryptedHelper->start();
} else {
_job = new DeleteJob(propagator()->account(),
propagator()->fullRemotePath(filename),
this);
- if (_deleteEncryptedHelper && !_deleteEncryptedHelper->folderToken().isEmpty()) {
- _job->setFolderToken(_deleteEncryptedHelper->folderToken());
- }
+
connect(_job.data(), &DeleteJob::finishedSignal, this, &PropagateRemoteDelete::slotDeleteJobFinished);
propagator()->_activeJobList.append(this);
_job->start();
propagator()->_journal->deleteFileRecord(_item->_originalFile, _item->isDirectory());
propagator()->_journal->commit("Remote Remove");
- if (_deleteEncryptedHelper && !_job->folderToken().isEmpty()) {
- propagator()->_activeJobList.append(this);
- connect(_deleteEncryptedHelper, &PropagateRemoteDeleteEncrypted::folderUnlocked,
- this, [this] {
- propagator()->_activeJobList.removeOne(this);
- done(SyncFileItem::Success);
- });
- _deleteEncryptedHelper->unlockFolder();
- } else {
- done(SyncFileItem::Success);
- }
+ done(SyncFileItem::Success);
}
}
namespace OCC {
-class PropagateRemoteDeleteEncrypted;
+class DeleteJob;
-/**
- * @brief The DeleteJob class
- * @ingroup libsync
- */
-class DeleteJob : public AbstractNetworkJob
-{
- Q_OBJECT
- QUrl _url; // Only used if the constructor taking a url is taken.
-public:
- explicit DeleteJob(AccountPtr account, const QString &path, QObject *parent = nullptr);
- explicit DeleteJob(AccountPtr account, const QUrl &url, QObject *parent = nullptr);
-
- void start() override;
- bool finished() override;
-
- QByteArray folderToken() const;
- void setFolderToken(const QByteArray &folderToken);
-
-signals:
- void finishedSignal();
-
-private:
- QByteArray _folderToken;
-};
+class AbstractPropagateRemoteDeleteEncrypted;
/**
* @brief The PropagateRemoteDelete class
{
Q_OBJECT
QPointer<DeleteJob> _job;
- PropagateRemoteDeleteEncrypted *_deleteEncryptedHelper = nullptr;
+ AbstractPropagateRemoteDeleteEncrypted *_deleteEncryptedHelper = nullptr;
public:
PropagateRemoteDelete(OwncloudPropagator *propagator, const SyncFileItemPtr &item)
+/*
+ * Copyright (C) by Oleksandr Zolotov <alex@nextcloud.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 "propagateremotedeleteencrypted.h"
#include "clientsideencryptionjobs.h"
-#include "clientsideencryption.h"
#include "owncloudpropagator.h"
-
+#include "encryptfolderjob.h"
#include <QLoggingCategory>
-#include <QMimeDatabase>
#include <QFileInfo>
-#include <QDir>
using namespace OCC;
Q_LOGGING_CATEGORY(PROPAGATE_REMOVE_ENCRYPTED, "nextcloud.sync.propagator.remove.encrypted")
PropagateRemoteDeleteEncrypted::PropagateRemoteDeleteEncrypted(OwncloudPropagator *propagator, SyncFileItemPtr item, QObject *parent)
- : QObject(parent)
- , _propagator(propagator)
- , _item(item)
+ : AbstractPropagateRemoteDeleteEncrypted(propagator, item, parent)
{
}
-QByteArray PropagateRemoteDeleteEncrypted::folderToken()
-{
- return _folderToken;
-}
-
void PropagateRemoteDeleteEncrypted::start()
{
Q_ASSERT(!_item->_encryptedFileName.isEmpty());
- QFileInfo info(_item->_encryptedFileName);
- qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Folder is encrypted, let's get the Id from it.";
- auto job = new LsColJob(_propagator->account(), info.path(), this);
- job->setProperties({"resourcetype", "http://owncloud.org/ns:fileid"});
- connect(job, &LsColJob::directoryListingSubfolders, this, &PropagateRemoteDeleteEncrypted::slotFolderEncryptedIdReceived);
- connect(job, &LsColJob::finishedWithError, this, &PropagateRemoteDeleteEncrypted::taskFailed);
- job->start();
-}
-void PropagateRemoteDeleteEncrypted::slotFolderEncryptedIdReceived(const QStringList &list)
-{
- qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Received id of folder, trying to lock it so we can prepare the metadata";
- auto job = qobject_cast<LsColJob *>(sender());
- const ExtraFolderInfo folderInfo = job->_folderInfos.value(list.first());
- slotTryLock(folderInfo.fileId);
+ const QFileInfo info(_item->_encryptedFileName);
+ startLsColJob(info.path());
}
-void PropagateRemoteDeleteEncrypted::slotTryLock(const QByteArray &folderId)
+void PropagateRemoteDeleteEncrypted::slotFolderUnLockedSuccessfully(const QByteArray &folderId)
{
- auto lockJob = new LockEncryptFolderApiJob(_propagator->account(), folderId, this);
- connect(lockJob, &LockEncryptFolderApiJob::success, this, &PropagateRemoteDeleteEncrypted::slotFolderLockedSuccessfully);
- connect(lockJob, &LockEncryptFolderApiJob::error, this, &PropagateRemoteDeleteEncrypted::taskFailed);
- lockJob->start();
-}
-
-void PropagateRemoteDeleteEncrypted::slotFolderLockedSuccessfully(const QByteArray &folderId, const QByteArray &token)
-{
- qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Folder id" << folderId << "Locked Successfully for Upload, Fetching Metadata";
- _folderLocked = true;
- _folderToken = token;
- _folderId = folderId;
-
- auto job = new GetMetadataApiJob(_propagator->account(), _folderId);
- connect(job, &GetMetadataApiJob::jsonReceived, this, &PropagateRemoteDeleteEncrypted::slotFolderEncryptedMetadataReceived);
- connect(job, &GetMetadataApiJob::error, this, &PropagateRemoteDeleteEncrypted::taskFailed);
- job->start();
+ AbstractPropagateRemoteDeleteEncrypted::slotFolderUnLockedSuccessfully(folderId);
+ emit finished(!_isTaskFailed);
}
void PropagateRemoteDeleteEncrypted::slotFolderEncryptedMetadataReceived(const QJsonDocument &json, int statusCode)
{
if (statusCode == 404) {
- qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Metadata not found, ignoring.";
- emit finished(true);
+ qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Metadata not found, but let's proceed with removing the file anyway.";
+ deleteRemoteItem(_item->_encryptedFileName);
return;
}
- qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Metadata Received, Preparing it for the new file.";
-
- // Encrypt File!
FolderMetadata metadata(_propagator->account(), json.toJson(QJsonDocument::Compact), statusCode);
- QFileInfo info(_propagator->fullLocalPath(_item->_file));
+ qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Metadata Received, preparing it for removal of the file";
+
+ const QFileInfo info(_propagator->fullLocalPath(_item->_file));
const QString fileName = info.fileName();
// Find existing metadata for this file
}
if (!found) {
- // The removed file was not in the JSON so nothing else to do
- emit finished(true);
+ // file is not found in the metadata, but we still need to remove it
+ deleteRemoteItem(_item->_encryptedFileName);
return;
}
qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Metadata updated, sending to the server.";
- auto job = new UpdateMetadataApiJob(_propagator->account(),
- _folderId,
- metadata.encryptedMetadata(),
- _folderToken);
-
- connect(job, &UpdateMetadataApiJob::success, this, [this] { emit finished(true); });
+ auto job = new UpdateMetadataApiJob(_propagator->account(), _folderId, metadata.encryptedMetadata(), _folderToken);
+ connect(job, &UpdateMetadataApiJob::success, this, [this](const QByteArray& fileId) {
+ Q_UNUSED(fileId);
+ deleteRemoteItem(_item->_encryptedFileName);
+ });
connect(job, &UpdateMetadataApiJob::error, this, &PropagateRemoteDeleteEncrypted::taskFailed);
job->start();
}
-
-void PropagateRemoteDeleteEncrypted::unlockFolder()
-{
- if (!_folderLocked) {
- emit folderUnlocked();
- return;
- }
-
- qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Unlocking folder" << _folderId;
- auto unlockJob = new UnlockEncryptFolderApiJob(_propagator->account(),
- _folderId, _folderToken, this);
-
- connect(unlockJob, &UnlockEncryptFolderApiJob::success, [this] {
- qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Folder successfully unlocked" << _folderId;
- _folderLocked = false;
- emit folderUnlocked();
- });
- connect(unlockJob, &UnlockEncryptFolderApiJob::error, this, &PropagateRemoteDeleteEncrypted::taskFailed);
- unlockJob->start();
-}
-
-void PropagateRemoteDeleteEncrypted::taskFailed()
-{
- qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Task failed of job" << sender();
- if (_folderLocked) {
- connect(this, &PropagateRemoteDeleteEncrypted::folderUnlocked, this, [this] { emit finished(false); });
- unlockFolder();
- } else {
- emit finished(false);
- }
-}
-#ifndef PROPAGATEREMOTEDELETEENCRYPTED_H
-#define PROPAGATEREMOTEDELETEENCRYPTED_H
-
-#include <QObject>
-#include <QElapsedTimer>
-
-#include "syncfileitem.h"
+/*
+ * Copyright (C) by Oleksandr Zolotov <alex@nextcloud.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 "abstractpropagateremotedeleteencrypted.h"
namespace OCC {
-class OwncloudPropagator;
-class PropagateRemoteDeleteEncrypted : public QObject
+class PropagateRemoteDeleteEncrypted : public AbstractPropagateRemoteDeleteEncrypted
{
Q_OBJECT
public:
- PropagateRemoteDeleteEncrypted(OwncloudPropagator *_propagator, SyncFileItemPtr item, QObject *parent);
-
- QByteArray folderToken();
- void unlockFolder();
+ PropagateRemoteDeleteEncrypted(OwncloudPropagator *propagator, SyncFileItemPtr item, QObject *parent);
- void start();
-
-signals:
- void finished(bool success);
- void folderUnlocked();
+ virtual void start() Q_DECL_OVERRIDE;
private:
- void slotFolderEncryptedIdReceived(const QStringList &list);
- void slotTryLock(const QByteArray &folderId);
- void slotFolderLockedSuccessfully(const QByteArray &fileId, const QByteArray &token);
- void slotFolderEncryptedMetadataReceived(const QJsonDocument &json, int statusCode);
- void taskFailed();
-
- OwncloudPropagator *_propagator;
- SyncFileItemPtr _item;
- QByteArray _folderToken;
- QByteArray _folderId;
- bool _folderLocked = false;
+ void slotFolderUnLockedSuccessfully(const QByteArray &folderId) override;
+ void slotFolderEncryptedMetadataReceived(const QJsonDocument &json, int statusCode) override;
};
}
-
-#endif // PROPAGATEREMOTEDELETEENCRYPTED_H
--- /dev/null
+/*
+ * Copyright (C) by Oleksandr Zolotov <alex@nextcloud.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.
+ */
+
+/*
+ * Removing the root encrypted folder is consisted of multiple steps:
+ * - 1st step is to obtain the folderID via LsColJob so it then can be used for the next step
+ * - 2nd step is to lock the root folder useing the folderID from the previous step. !!! NOTE: If there are no nested items in the folder, this, and subsequent steps are skipped until step 7.
+ * - 3rd step is to obtain the root folder's metadata (it contains list of nested files and folders)
+ * - 4th step is to remove the nested files and folders from the metadata and send it to the server via UpdateMetadataApiJob
+ * - 5th step is to trigger DeleteJob for every nested file and folder of the root folder
+ * - 6th step is to unlock the root folder using the previously obtained token from locking
+ * - 7th step is to decrypt and delete the root folder, because it is now possible as it has become empty
+ */
+
+#include <QFileInfo>
+#include <QLoggingCategory>
+
+#include "deletejob.h"
+#include "clientsideencryptionjobs.h"
+#include "clientsideencryption.h"
+#include "encryptfolderjob.h"
+#include "owncloudpropagator.h"
+#include "propagateremotedeleteencryptedrootfolder.h"
+
+namespace {
+ const char* encryptedFileNamePropertyKey = "encryptedFileName";
+}
+
+using namespace OCC;
+
+Q_LOGGING_CATEGORY(PROPAGATE_REMOVE_ENCRYPTED_ROOTFOLDER, "nextcloud.sync.propagator.remove.encrypted.rootfolder")
+
+PropagateRemoteDeleteEncryptedRootFolder::PropagateRemoteDeleteEncryptedRootFolder(OwncloudPropagator *propagator, SyncFileItemPtr item, QObject *parent)
+ : AbstractPropagateRemoteDeleteEncrypted(propagator, item, parent)
+{
+
+}
+
+void PropagateRemoteDeleteEncryptedRootFolder::start()
+{
+ Q_ASSERT(_item->_isEncrypted);
+
+ const bool listFilesResult = _propagator->_journal->listFilesInPath(_item->_file.toUtf8(), [this](const OCC::SyncJournalFileRecord &record) {
+ _nestedItems[record._e2eMangledName] = record;
+ });
+
+ if (!listFilesResult || _nestedItems.isEmpty()) {
+ // if the folder is empty, just decrypt and delete it
+ decryptAndRemoteDelete();
+ return;
+ }
+
+ startLsColJob(_item->_file);
+}
+
+void PropagateRemoteDeleteEncryptedRootFolder::slotFolderUnLockedSuccessfully(const QByteArray &folderId)
+{
+ AbstractPropagateRemoteDeleteEncrypted::slotFolderUnLockedSuccessfully(folderId);
+ decryptAndRemoteDelete();
+}
+
+void PropagateRemoteDeleteEncryptedRootFolder::slotFolderEncryptedMetadataReceived(const QJsonDocument &json, int statusCode)
+{
+ if (statusCode == 404) {
+ // we've eneded up having no metadata, but, _nestedItems is not empty since we went this far, let's proceed with removing the nested items without modifying the metadata
+ qCDebug(PROPAGATE_REMOVE_ENCRYPTED_ROOTFOLDER) << "There is no metadata for this folder. Just remove it's nested items.";
+ for (auto it = _nestedItems.constBegin(); it != _nestedItems.constEnd(); ++it) {
+ deleteNestedRemoteItem(it.key());
+ }
+ return;
+ }
+
+ FolderMetadata metadata(_propagator->account(), json.toJson(QJsonDocument::Compact), statusCode);
+
+ qCDebug(PROPAGATE_REMOVE_ENCRYPTED_ROOTFOLDER) << "It's a root encrypted folder. Let's remove nested items first.";
+
+ metadata.removeAllEncryptedFiles();
+
+ qCDebug(PROPAGATE_REMOVE_ENCRYPTED_ROOTFOLDER) << "Metadata updated, sending to the server.";
+
+ auto job = new UpdateMetadataApiJob(_propagator->account(), _folderId, metadata.encryptedMetadata(), _folderToken);
+ connect(job, &UpdateMetadataApiJob::success, this, [this](const QByteArray& fileId) {
+ Q_UNUSED(fileId);
+ for (auto it = _nestedItems.constBegin(); it != _nestedItems.constEnd(); ++it) {
+ deleteNestedRemoteItem(it.key());
+ }
+ });
+ connect(job, &UpdateMetadataApiJob::error, this, &PropagateRemoteDeleteEncryptedRootFolder::taskFailed);
+ job->start();
+}
+
+void PropagateRemoteDeleteEncryptedRootFolder::slotDeleteNestedRemoteItemFinished()
+{
+ auto *deleteJob = qobject_cast<DeleteJob *>(QObject::sender());
+
+ Q_ASSERT(deleteJob);
+
+ if (!deleteJob) {
+ return;
+ }
+
+ const QString encryptedFileName = deleteJob->property(encryptedFileNamePropertyKey).toString();
+
+ if (!encryptedFileName.isEmpty()) {
+ const auto nestedItem = _nestedItems.take(encryptedFileName);
+
+ if (nestedItem.isValid()) {
+ _propagator->_journal->deleteFileRecord(nestedItem._path, nestedItem._type == ItemTypeDirectory);
+ _propagator->_journal->commit("Remote Remove");
+ }
+ }
+
+ QNetworkReply::NetworkError err = deleteJob->reply()->error();
+
+ const auto httpErrorCode = deleteJob->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+ _item->_responseTimeStamp = deleteJob->responseTimestamp();
+ _item->_requestId = deleteJob->requestId();
+
+ if (err != QNetworkReply::NoError && err != QNetworkReply::ContentNotFoundError) {
+ storeFirstError(err);
+ storeFirstErrorString(deleteJob->errorString());
+ qCWarning(PROPAGATE_REMOVE_ENCRYPTED_ROOTFOLDER) << "Delete nested item finished with error" << err << ".";
+ } else if (httpErrorCode != 204 && httpErrorCode != 404) {
+ // A 404 reply is also considered a success here: We want to make sure
+ // a file is gone from the server. It not being there in the first place
+ // is ok. This will happen for files that are in the DB but not on
+ // the server or the local file system.
+
+ // Normally we expect "204 No Content"
+ // If it is not the case, it might be because of a proxy or gateway intercepting the request, so we must
+ // throw an error.
+ storeFirstErrorString(tr("Wrong HTTP code returned by server. Expected 204, but received \"%1 %2\".")
+ .arg(httpErrorCode)
+ .arg(deleteJob->reply()->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString()));
+ if (_item->_httpErrorCode == 0) {
+ _item->_httpErrorCode = httpErrorCode;
+ }
+
+ qCWarning(PROPAGATE_REMOVE_ENCRYPTED_ROOTFOLDER) << "Delete nested item finished with error" << httpErrorCode << ".";
+ }
+
+ if (_nestedItems.size() == 0) {
+ // we wait for all _nestedItems' DeleteJobs to finish, and then - fail if any of those jobs has failed
+ if (networkError() != QNetworkReply::NetworkError::NoError || _item->_httpErrorCode != 0) {
+ const int errorCode = networkError() != QNetworkReply::NetworkError::NoError ? networkError() : _item->_httpErrorCode;
+ qCCritical(PROPAGATE_REMOVE_ENCRYPTED_ROOTFOLDER) << "Delete of nested items finished with error" << errorCode << ". Failing the entire sequence.";
+ taskFailed();
+ return;
+ }
+ unlockFolder();
+ }
+}
+
+void PropagateRemoteDeleteEncryptedRootFolder::deleteNestedRemoteItem(const QString &filename)
+{
+ qCInfo(PROPAGATE_REMOVE_ENCRYPTED_ROOTFOLDER) << "Deleting nested encrypted remote item" << filename;
+
+ auto deleteJob = new DeleteJob(_propagator->account(), _propagator->fullRemotePath(filename), this);
+ deleteJob->setFolderToken(_folderToken);
+ deleteJob->setProperty(encryptedFileNamePropertyKey, filename);
+
+ connect(deleteJob, &DeleteJob::finishedSignal, this, &PropagateRemoteDeleteEncryptedRootFolder::slotDeleteNestedRemoteItemFinished);
+
+ deleteJob->start();
+}
+
+void PropagateRemoteDeleteEncryptedRootFolder::decryptAndRemoteDelete()
+{
+ auto job = new OCC::SetEncryptionFlagApiJob(_propagator->account(), _item->_fileId, OCC::SetEncryptionFlagApiJob::Clear, this);
+ connect(job, &OCC::SetEncryptionFlagApiJob::success, this, [this] (const QByteArray &fileId) {
+ Q_UNUSED(fileId);
+ deleteRemoteItem(_item->_file);
+ });
+ connect(job, &OCC::SetEncryptionFlagApiJob::error, this, [this] (const QByteArray &fileId, int httpReturnCode) {
+ Q_UNUSED(fileId);
+ _item->_httpErrorCode = httpReturnCode;
+ taskFailed();
+ });
+ job->start();
+}
--- /dev/null
+/*
+ * Copyright (C) by Oleksandr Zolotov <alex@nextcloud.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 <QMap>
+
+#include "abstractpropagateremotedeleteencrypted.h"
+#include "syncfileitem.h"
+
+namespace OCC {
+
+class PropagateRemoteDeleteEncryptedRootFolder : public AbstractPropagateRemoteDeleteEncrypted
+{
+ Q_OBJECT
+public:
+ PropagateRemoteDeleteEncryptedRootFolder(OwncloudPropagator *propagator, SyncFileItemPtr item, QObject *parent);
+
+ virtual void start() Q_DECL_OVERRIDE;
+
+private:
+ void slotFolderUnLockedSuccessfully(const QByteArray &folderId) override;
+ void slotFolderEncryptedMetadataReceived(const QJsonDocument &json, int statusCode) override;
+ void slotDeleteNestedRemoteItemFinished();
+
+ void deleteNestedRemoteItem(const QString &filename);
+ void decryptAndRemoteDelete();
+
+ QMap<QString, OCC::SyncJournalFileRecord> _nestedItems; // Nested files and folders
+};
+
+}
#include "account.h"
#include "common/syncjournalfilerecord.h"
#include "propagateuploadencrypted.h"
-#include "propagateremotedelete.h"
+#include "deletejob.h"
#include "common/asserts.h"
#include "encryptfolderjob.h"
#include "propagatorjobs.h"
#include "common/checksums.h"
#include "syncengine.h"
-#include "propagateremotedelete.h"
+#include "deletejob.h"
#include "common/asserts.h"
#include "networkjobs.h"
#include "clientsideencryption.h"
#include "propagatorjobs.h"
#include "syncengine.h"
#include "propagateremotemove.h"
-#include "propagateremotedelete.h"
+#include "deletejob.h"
#include "common/asserts.h"
#include <QNetworkAccessManager>
#include "common/syncfilestatus.h"
#include "csync_exclude.h"
#include "filesystem.h"
-#include "propagateremotedelete.h"
+#include "deletejob.h"
#include "propagatedownload.h"
#include "common/asserts.h"
#include "configfile.h"