diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp index 4e297847ee50d..8c4e54c6e0a16 100644 --- a/src/gui/folder.cpp +++ b/src/gui/folder.cpp @@ -1353,7 +1353,7 @@ void Folder::removeLocalE2eFiles() const auto currentSyncPaused = syncPaused(); setSyncPaused(true); - qCDebug(lcFolder) << "About to remove: " << e2eFiles; + qCInfo(lcFolder) << "About to remove: " << e2eFiles; for (const auto &e2eFilePath : qAsConst(e2eFiles)) { if (!_journal.deleteFileRecord(e2eFilePath, true)) { @@ -1362,7 +1362,7 @@ void Folder::removeLocalE2eFiles() continue; } - qCDebug(lcFolder) << "Removing local copy of" << e2eFilePath; + qCInfo(lcFolder) << "Removing local copy of" << e2eFilePath; const auto fullPath = QString(path() + e2eFilePath); const QFileInfo pathInfo(fullPath); diff --git a/test/syncenginetestutils.cpp b/test/syncenginetestutils.cpp index e36c4f7a8b589..3cf3628e39223 100644 --- a/test/syncenginetestutils.cpp +++ b/test/syncenginetestutils.cpp @@ -108,6 +108,10 @@ void DiskFileModifier::modifyLockState([[maybe_unused]] const QString &relativeP { } +void DiskFileModifier::setE2EE([[maybe_unused]] const QString &relativePath, [[maybe_unused]] const bool enable) +{ +} + FileInfo FileInfo::A12_B12_C12_S12() { FileInfo fi { QString {}, { @@ -213,6 +217,13 @@ void FileInfo::modifyLockState(const QString &relativePath, LockState lockState, file->lockTimeout = lockTimeout; } +void FileInfo::setE2EE(const QString &relativePath, const bool enable) +{ + FileInfo *file = findInvalidatingEtags(relativePath); + Q_ASSERT(file); + file->isEncrypted = enable; +} + FileInfo *FileInfo::find(PathComponents pathComponents, const bool invalidateEtags) { if (pathComponents.isEmpty()) { @@ -366,6 +377,7 @@ FakePropfindReply::FakePropfindReply(FileInfo &remoteRootFileInfo, QNetworkAcces xml.writeTextElement(ncUri, QStringLiteral("lock-owner-editor"), fileInfo.lockOwnerId); xml.writeTextElement(ncUri, QStringLiteral("lock-time"), QString::number(fileInfo.lockTime)); xml.writeTextElement(ncUri, QStringLiteral("lock-timeout"), QString::number(fileInfo.lockTimeout)); + xml.writeTextElement(ncUri, QStringLiteral("is-encrypted"), fileInfo.isEncrypted ? QString::number(1) : QString::number(0)); buffer.write(fileInfo.extraDavProperties); xml.writeEndElement(); // prop xml.writeTextElement(davUri, QStringLiteral("status"), QStringLiteral("HTTP/1.1 200 OK")); @@ -1000,11 +1012,13 @@ QNetworkReply *FakeQNAM::createRequest(QNetworkAccessManager::Operation op, cons newRequest.setRawHeader("X-Request-ID", OCC::AccessManager::generateRequestId()); auto contentType = request.header(QNetworkRequest::ContentTypeHeader).toString(); if (_override) { + qDebug() << "Using override!"; if (auto _reply = _override(op, newRequest, outgoingData)) { reply = _reply; } } if (!reply) { + qDebug() << newRequest.url(); reply = overrideReplyWithError(getFilePathFromUrl(newRequest.url()), op, newRequest); } if (!reply) { diff --git a/test/syncenginetestutils.h b/test/syncenginetestutils.h index b0b8e1eed77f9..7f1c3e2f6248f 100644 --- a/test/syncenginetestutils.h +++ b/test/syncenginetestutils.h @@ -90,6 +90,7 @@ class FileModifier virtual void rename(const QString &relativePath, const QString &relativeDestinationDirectory) = 0; virtual void setModTime(const QString &relativePath, const QDateTime &modTime) = 0; virtual void modifyLockState(const QString &relativePath, LockState lockState, int lockType, const QString &lockOwner, const QString &lockOwnerId, const QString &lockEditorId, quint64 lockTime, quint64 lockTimeout) = 0; + virtual void setE2EE(const QString &relativepath, const bool enabled) = 0; }; class DiskFileModifier : public FileModifier @@ -106,6 +107,7 @@ class DiskFileModifier : public FileModifier void rename(const QString &from, const QString &to) override; void setModTime(const QString &relativePath, const QDateTime &modTime) override; void modifyLockState(const QString &relativePath, LockState lockState, int lockType, const QString &lockOwner, const QString &lockOwnerId, const QString &lockEditorId, quint64 lockTime, quint64 lockTimeout) override; + void setE2EE(const QString &relativepath, const bool enabled) override; }; class FileInfo : public FileModifier @@ -140,6 +142,8 @@ class FileInfo : public FileModifier void modifyLockState(const QString &relativePath, LockState lockState, int lockType, const QString &lockOwner, const QString &lockOwnerId, const QString &lockEditorId, quint64 lockTime, quint64 lockTimeout) override; + void setE2EE(const QString &relativepath, const bool enabled) override; + FileInfo *find(PathComponents pathComponents, const bool invalidateEtags = false); FileInfo *createDir(const QString &relativePath); @@ -180,6 +184,7 @@ class FileInfo : public FileModifier QString lockEditorId; quint64 lockTime = 0; quint64 lockTimeout = 0; + bool isEncrypted = false; // Sorted by name to be able to compare trees QMap children; diff --git a/test/testfolderman.cpp b/test/testfolderman.cpp index 8c348945e9c3a..58dc065a594ff 100644 --- a/test/testfolderman.cpp +++ b/test/testfolderman.cpp @@ -9,6 +9,7 @@ #include #include +#include "QtTest/qtestcase.h" #include "common/utility.h" #include "folderman.h" #include "account.h" @@ -20,6 +21,14 @@ using namespace OCC; +bool itemDidCompleteSuccessfully(const ItemCompletedSpy &spy, const QString &path) +{ + if (auto item = spy.findItem(path)) { + return item->_status == SyncFileItem::Success; + } + return false; +} + class TestFolderMan: public QObject { Q_OBJECT @@ -311,7 +320,91 @@ private slots: QCOMPARE(folderman->findGoodPathForNewSyncFolder(dirPath + "/ownCloud2", url), QString(dirPath + "/ownCloud22")); } + + void testDeleteEncryptedFiles() + { + FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; + QCOMPARE(fakeFolder.currentLocalState().children.count(), 4); + + ItemCompletedSpy completeSpy(fakeFolder); + fakeFolder.localModifier().mkdir("encrypted"); + fakeFolder.localModifier().setE2EE("encrypted", true); + fakeFolder.remoteModifier().mkdir("encrypted"); + fakeFolder.remoteModifier().setE2EE("encrypted", true); + + const auto fakeFileInfo = fakeFolder.remoteModifier().find("encrypted"); + QVERIFY(fakeFileInfo); + QCOMPARE(fakeFolder.currentLocalState().children.count(), 5); + + const auto fakeFileId = fakeFileInfo->fileId; + const auto fakeQnam = new FakeQNAM({}); + // Let's avoid the null filename assert in the default FakeQNAM request creation + const auto fakeQnamOverride = [this, fakeFileId](const QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *device) { + Q_UNUSED(device) + QNetworkReply *reply = nullptr; + + const auto reqUrl = req.url(); + const auto reqRawPath = reqUrl.path(); + const auto reqPath = reqRawPath.startsWith("/owncloud/") ? reqRawPath.mid(10) : reqRawPath; + + if (reqPath.startsWith(QStringLiteral("ocs/v2.php/apps/end_to_end_encryption/api/v1/meta-data/"))) { + const auto splitUrlPath = reqPath.split('/'); + const auto fileId = splitUrlPath.last(); + + const QUrlQuery urlQuery(req.url()); + const auto formatParam = urlQuery.queryItemValue(QStringLiteral("format")); + + if(fileId == fakeFileId && formatParam == QStringLiteral("json")) { + reply = new FakePayloadReply(op, req, QJsonDocument().toJson(), this); + } + } + return reply; + }; + fakeFolder.setServerOverride(fakeQnamOverride); + fakeQnam->setOverride(fakeQnamOverride); + + const auto account = Account::create(); + const auto capabilities = QVariantMap { + {QStringLiteral("end-to-end-encryption"), QVariantMap { + {QStringLiteral("enabled"), true}, + {QStringLiteral("api-version"), QString::number(2.0)}, + }}, + }; + account->setCapabilities(capabilities); + account->setCredentials(new FakeCredentials{fakeQnam}); + account->setUrl(QUrl(("owncloud://somehost/owncloud"))); + const auto accountState = new FakeAccountState(account); + QVERIFY(accountState->isConnected()); + + QVERIFY(fakeFolder.syncOnce()); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + const auto folder = FolderMan::instance()->addFolder(accountState, folderDefinition(fakeFolder.localPath())); + QVERIFY(folder); + QSignalSpy folderSyncDone(folder, &Folder::syncFinished); + + QDir dir(folder->path() + QStringLiteral("encrypted")); + QVERIFY(dir.exists()); + QVERIFY(fakeFolder.remoteModifier().find("encrypted")); + QVERIFY(fakeFolder.currentLocalState().find("encrypted")); + QCOMPARE(fakeFolder.currentLocalState().children.count(), 5); + + // Rather than go through the pain of trying to replicate the E2EE response from + // the server, let's just manually set the encryption bool in the folder journal + SyncJournalFileRecord rec; + QVERIFY(folder->journalDb()->getFileRecord(QStringLiteral("encrypted"), &rec)); + rec._isE2eEncrypted = true; + rec._path = QStringLiteral("encrypted").toUtf8(); + QVERIFY(folder->journalDb()->setFileRecord(rec)); + FolderMan::instance()->removeE2eFiles(account); + + QVERIFY(folderSyncDone.wait()); + + QVERIFY(fakeFolder.currentRemoteState().find("encrypted")); + QVERIFY(!fakeFolder.currentLocalState().find("encrypted")); + QCOMPARE(fakeFolder.currentLocalState().children.count(), 4); + } }; -QTEST_GUILESS_MAIN(TestFolderMan) +QTEST_MAIN(TestFolderMan) #include "testfolderman.moc"