558 lines
30 KiB
C++
558 lines
30 KiB
C++
/*
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
#include <QtTest>
|
|
#include "testutils/syncenginetestutils.h"
|
|
#include <syncengine.h>
|
|
#include "common/ownsql.h"
|
|
|
|
using namespace OCC;
|
|
|
|
static void applyPermissionsFromName(FileInfo &info) {
|
|
static QRegularExpression rx(QStringLiteral("_PERM_([^_]*)_[^/]*$"));
|
|
auto m = rx.match(info.name);
|
|
if (m.hasMatch()) {
|
|
info.permissions = RemotePermissions::fromServerString(m.captured(1));
|
|
}
|
|
|
|
for (FileInfo &sub : info.children)
|
|
applyPermissionsFromName(sub);
|
|
}
|
|
|
|
// Check if the expected rows in the DB are non-empty. Note that in some cases they might be, then we cannot use this function
|
|
// https://github.com/owncloud/client/issues/2038
|
|
static void assertCsyncJournalOk(SyncJournalDb &journal)
|
|
{
|
|
// The DB is openend in locked mode: close to allow us to access.
|
|
journal.close();
|
|
|
|
SqlDatabase db;
|
|
QVERIFY(db.openReadOnly(journal.databaseFilePath()));
|
|
SqlQuery q("SELECT count(*) from metadata where length(fileId) == 0", db);
|
|
QVERIFY(q.exec());
|
|
QVERIFY(q.next().hasData);
|
|
QCOMPARE(q.intValue(0), 0);
|
|
#if defined(Q_OS_WIN) // Make sure the file does not appear in the FileInfo
|
|
FileSystem::setFileHidden(journal.databaseFilePath() + QStringLiteral("-shm"), true);
|
|
#endif
|
|
journal.allowReopen();
|
|
}
|
|
|
|
SyncFileItemPtr findDiscoveryItem(const SyncFileItemSet &spy, const QString &path)
|
|
{
|
|
for (const auto &item : spy) {
|
|
if (item->destination() == path)
|
|
return item;
|
|
}
|
|
return SyncFileItemPtr(new SyncFileItem);
|
|
}
|
|
|
|
bool itemInstruction(const ItemCompletedSpy &spy, const QString &path, const SyncInstructions instr)
|
|
{
|
|
auto item = spy.findItem(path);
|
|
Q_ASSERT(!item.isNull());
|
|
return item->instruction() == instr;
|
|
}
|
|
|
|
bool discoveryInstruction(const SyncFileItemSet &spy, const QString &path, const SyncInstructions instr)
|
|
{
|
|
auto item = findDiscoveryItem(spy, path);
|
|
return item->instruction() == instr;
|
|
}
|
|
|
|
class TestPermissions : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
private Q_SLOTS:
|
|
void initTestCase_data()
|
|
{
|
|
QTest::addColumn<Vfs::Mode>("vfsMode");
|
|
QTest::addColumn<bool>("filesAreDehydrated");
|
|
|
|
QTest::newRow("Vfs::Mode::Off") << Vfs::Mode::Off << false;
|
|
|
|
if (VfsPluginManager::instance().isVfsPluginAvailable(Vfs::Mode::WindowsCfApi)) {
|
|
QTest::newRow("Vfs::Mode::WindowsCfApi dehydrated") << Vfs::Mode::WindowsCfApi << true;
|
|
|
|
// TODO: the hydrated version will fail due to an issue in the winvfs plugin, so leave it disabled for now.
|
|
// QTest::newRow("Vfs::Mode::WindowsCfApi hydrated") << Vfs::Mode::WindowsCfApi << false;
|
|
} else if (Utility::isWindows()) {
|
|
qWarning("Skipping Vfs::Mode::WindowsCfApi");
|
|
}
|
|
}
|
|
|
|
void t7pl()
|
|
{
|
|
QFETCH_GLOBAL(Vfs::Mode, vfsMode);
|
|
QFETCH_GLOBAL(bool, filesAreDehydrated);
|
|
|
|
if (vfsMode == Vfs::Mode::WindowsCfApi) {
|
|
QSKIP("Known issue: the winvfs plug-in fails to set file permissions");
|
|
}
|
|
|
|
FakeFolder fakeFolder(FileInfo(), vfsMode, filesAreDehydrated);
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
|
|
// Some of this test depends on the order of discovery. With threading
|
|
// that order becomes effectively random, but we want to make sure to test
|
|
// all cases and thus disable threading.
|
|
auto syncOpts = fakeFolder.syncEngine().syncOptions();
|
|
syncOpts._parallelNetworkJobs = [] { return 1; };
|
|
fakeFolder.syncEngine().setSyncOptions(syncOpts);
|
|
|
|
const auto cannotBeModifiedSize = 133_B;
|
|
const auto canBeModifiedSize = 144_B;
|
|
|
|
//create some files
|
|
auto insertIn = [&](const QString &dir) {
|
|
fakeFolder.remoteModifier().insert(dir + QStringLiteral("normalFile_PERM_WVND_.data"), 100_B);
|
|
fakeFolder.remoteModifier().insert(dir + QStringLiteral("cannotBeRemoved_PERM_WVN_.data"), 101_B);
|
|
fakeFolder.remoteModifier().insert(dir + QStringLiteral("canBeRemoved_PERM_D_.data"), 102_B);
|
|
fakeFolder.remoteModifier().insert(dir + QStringLiteral("cannotBeModified_PERM_DVN_.data"), cannotBeModifiedSize, 'A');
|
|
fakeFolder.remoteModifier().insert(dir + QStringLiteral("canBeModified_PERM_W_.data"), canBeModifiedSize);
|
|
};
|
|
|
|
//put them in some directories
|
|
fakeFolder.remoteModifier().mkdir(QStringLiteral("normalDirectory_PERM_CKDNV_"));
|
|
insertIn(QStringLiteral("normalDirectory_PERM_CKDNV_/"));
|
|
fakeFolder.remoteModifier().mkdir(QStringLiteral("readonlyDirectory_PERM_M_"));
|
|
insertIn(QStringLiteral("readonlyDirectory_PERM_M_/"));
|
|
fakeFolder.remoteModifier().mkdir(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_"));
|
|
fakeFolder.remoteModifier().mkdir(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_"));
|
|
fakeFolder.remoteModifier().insert(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data"), 100_B);
|
|
applyPermissionsFromName(fakeFolder.remoteModifier());
|
|
|
|
QVERIFY(fakeFolder.applyLocalModificationsAndSync());
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
assertCsyncJournalOk(fakeFolder.syncJournal());
|
|
qInfo("Do some changes and see how they propagate");
|
|
|
|
//1. remove the file than cannot be removed
|
|
// (they should be recovered)
|
|
fakeFolder.localModifier().remove(QStringLiteral("normalDirectory_PERM_CKDNV_/cannotBeRemoved_PERM_WVN_.data"));
|
|
fakeFolder.localModifier().remove(QStringLiteral("readonlyDirectory_PERM_M_/cannotBeRemoved_PERM_WVN_.data"));
|
|
|
|
//2. remove the file that can be removed
|
|
// (they should properly be gone)
|
|
auto removeReadOnly = [&] (const QString &file) {
|
|
QVERIFY(!QFileInfo(fakeFolder.localPath() + file).permission(QFile::WriteOwner));
|
|
QFile(fakeFolder.localPath() + file).setPermissions(QFile::WriteOwner | QFile::ReadOwner);
|
|
fakeFolder.localModifier().remove(file);
|
|
};
|
|
removeReadOnly(QStringLiteral("normalDirectory_PERM_CKDNV_/canBeRemoved_PERM_D_.data"));
|
|
removeReadOnly(QStringLiteral("readonlyDirectory_PERM_M_/canBeRemoved_PERM_D_.data"));
|
|
|
|
//3. Edit the files that cannot be modified
|
|
// (they should be recovered, and a conflict shall be created)
|
|
auto editReadOnly = [&] (const QString &file) {
|
|
QVERIFY(!QFileInfo(fakeFolder.localPath() + file).permission(QFile::WriteOwner));
|
|
QFile(fakeFolder.localPath() + file).setPermissions(QFile::WriteOwner | QFile::ReadOwner);
|
|
fakeFolder.localModifier().appendByte(file);
|
|
};
|
|
editReadOnly(QStringLiteral("normalDirectory_PERM_CKDNV_/cannotBeModified_PERM_DVN_.data"));
|
|
editReadOnly(QStringLiteral("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data"));
|
|
|
|
//4. Edit other files
|
|
// (they should be uploaded)
|
|
fakeFolder.localModifier().appendByte(QStringLiteral("normalDirectory_PERM_CKDNV_/canBeModified_PERM_W_.data"));
|
|
fakeFolder.localModifier().appendByte(QStringLiteral("readonlyDirectory_PERM_M_/canBeModified_PERM_W_.data"));
|
|
|
|
//5. Create a new file in a read write folder
|
|
// (should be uploaded)
|
|
fakeFolder.localModifier().insert(QStringLiteral("normalDirectory_PERM_CKDNV_/newFile_PERM_WDNV_.data"), 106_B);
|
|
applyPermissionsFromName(fakeFolder.remoteModifier());
|
|
|
|
//do the sync
|
|
QVERIFY(fakeFolder.applyLocalModificationsAndSync());
|
|
assertCsyncJournalOk(fakeFolder.syncJournal());
|
|
auto currentLocalState = fakeFolder.currentLocalState();
|
|
|
|
//1.
|
|
// File should be recovered
|
|
QVERIFY(currentLocalState.find(QStringLiteral("normalDirectory_PERM_CKDNV_/cannotBeRemoved_PERM_WVN_.data")));
|
|
QVERIFY(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/cannotBeRemoved_PERM_WVN_.data")));
|
|
|
|
//2.
|
|
// File should be deleted
|
|
QVERIFY(!currentLocalState.find(QStringLiteral("normalDirectory_PERM_CKDNV_/canBeRemoved_PERM_D_.data")));
|
|
QVERIFY(!currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/canBeRemoved_PERM_D_.data")));
|
|
|
|
//3.
|
|
// File should be recovered
|
|
QCOMPARE(currentLocalState.find(QStringLiteral("normalDirectory_PERM_CKDNV_/cannotBeModified_PERM_DVN_.data"))->contentSize, cannotBeModifiedSize);
|
|
QCOMPARE(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data"))->contentSize, cannotBeModifiedSize);
|
|
// and conflict created
|
|
auto c1 = findConflict(currentLocalState, QStringLiteral("normalDirectory_PERM_CKDNV_/cannotBeModified_PERM_DVN_.data"));
|
|
QVERIFY(c1);
|
|
QCOMPARE(c1->contentSize, cannotBeModifiedSize + 1);
|
|
auto c2 = findConflict(currentLocalState, QStringLiteral("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data"));
|
|
QVERIFY(c2);
|
|
QCOMPARE(c2->contentSize, cannotBeModifiedSize + 1);
|
|
// remove the conflicts for the next state comparison
|
|
fakeFolder.localModifier().remove(c1->path());
|
|
fakeFolder.localModifier().remove(c2->path());
|
|
QVERIFY(fakeFolder.applyLocalModificationsAndSync());
|
|
|
|
//4. File should be updated, that's tested by assertLocalAndRemoteDir
|
|
QCOMPARE(currentLocalState.find(QStringLiteral("normalDirectory_PERM_CKDNV_/canBeModified_PERM_W_.data"))->contentSize, canBeModifiedSize + 1);
|
|
QCOMPARE(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/canBeModified_PERM_W_.data"))->contentSize, canBeModifiedSize + 1);
|
|
|
|
//5.
|
|
// the file should be in the server and local
|
|
QVERIFY(currentLocalState.find(QStringLiteral("normalDirectory_PERM_CKDNV_/newFile_PERM_WDNV_.data")));
|
|
|
|
// Both side should still be the same
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
|
|
// Next test
|
|
|
|
//6. Create a new file in a read only folder
|
|
// (they should not be uploaded)
|
|
fakeFolder.localModifier().insert(QStringLiteral("readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data"), 105_B);
|
|
|
|
applyPermissionsFromName(fakeFolder.remoteModifier());
|
|
// error: can't upload to readonly
|
|
QVERIFY(!fakeFolder.applyLocalModificationsAndSync());
|
|
|
|
assertCsyncJournalOk(fakeFolder.syncJournal());
|
|
currentLocalState = fakeFolder.currentLocalState();
|
|
|
|
//6.
|
|
// The file should not exist on the remote, but still be there
|
|
QVERIFY(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data")));
|
|
QVERIFY(!fakeFolder.currentRemoteState().find(QStringLiteral("readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data")));
|
|
// remove it so next test succeed.
|
|
fakeFolder.localModifier().remove(QStringLiteral("readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data"));
|
|
QVERIFY(fakeFolder.applyLocalModificationsAndSync());
|
|
// Both side should still be the same
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
|
|
|
|
//######################################################################
|
|
qInfo( "remove the read only directory" );
|
|
// -> It must be recovered
|
|
fakeFolder.localModifier().remove(QStringLiteral("readonlyDirectory_PERM_M_"));
|
|
applyPermissionsFromName(fakeFolder.remoteModifier());
|
|
QVERIFY(fakeFolder.applyLocalModificationsAndSync());
|
|
assertCsyncJournalOk(fakeFolder.syncJournal());
|
|
currentLocalState = fakeFolder.currentLocalState();
|
|
QVERIFY(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/cannotBeRemoved_PERM_WVN_.data")));
|
|
QVERIFY(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_")));
|
|
// the subdirectory had delete permissions, so the contents were deleted
|
|
QVERIFY(!currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_")));
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
|
|
// restore
|
|
fakeFolder.remoteModifier().mkdir(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_"));
|
|
fakeFolder.remoteModifier().insert(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data"));
|
|
applyPermissionsFromName(fakeFolder.remoteModifier());
|
|
QVERIFY(fakeFolder.applyLocalModificationsAndSync());
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
|
|
|
|
//######################################################################
|
|
qInfo( "move a directory in a outside read only folder" );
|
|
|
|
//Missing directory should be restored
|
|
//new directory should be uploaded
|
|
fakeFolder.localModifier().rename(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_"), QStringLiteral("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_"));
|
|
applyPermissionsFromName(fakeFolder.remoteModifier());
|
|
QVERIFY(fakeFolder.applyLocalModificationsAndSync());
|
|
currentLocalState = fakeFolder.currentLocalState();
|
|
|
|
// old name restored
|
|
QVERIFY(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_")));
|
|
// contents moved (had move permissions)
|
|
QVERIFY(!currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_")));
|
|
QVERIFY(!currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data")));
|
|
|
|
// new still exist (and is uploaded)
|
|
QVERIFY(currentLocalState.find(QStringLiteral("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data")));
|
|
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
|
|
// restore for further tests
|
|
fakeFolder.remoteModifier().mkdir(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_"));
|
|
fakeFolder.remoteModifier().insert(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data"));
|
|
applyPermissionsFromName(fakeFolder.remoteModifier());
|
|
QVERIFY(fakeFolder.applyLocalModificationsAndSync());
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
|
|
|
|
//######################################################################
|
|
qInfo( "rename a directory in a read only folder and move a directory to a read-only" );
|
|
|
|
// do a sync to update the database
|
|
applyPermissionsFromName(fakeFolder.remoteModifier());
|
|
QVERIFY(fakeFolder.applyLocalModificationsAndSync());
|
|
assertCsyncJournalOk(fakeFolder.syncJournal());
|
|
|
|
QVERIFY(
|
|
fakeFolder.currentLocalState().find(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data")));
|
|
|
|
//1. rename a directory in a read only folder
|
|
//Missing directory should be restored
|
|
//new directory should stay but not be uploaded
|
|
fakeFolder.localModifier().rename(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_"), QStringLiteral("readonlyDirectory_PERM_M_/newname_PERM_CK_"));
|
|
|
|
//2. move a directory from read to read only (move the directory from previous step)
|
|
fakeFolder.localModifier().rename(QStringLiteral("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_"), QStringLiteral("readonlyDirectory_PERM_M_/moved_PERM_CK_"));
|
|
|
|
// error: can't upload to readonly!
|
|
QVERIFY(!fakeFolder.applyLocalModificationsAndSync());
|
|
currentLocalState = fakeFolder.currentLocalState();
|
|
|
|
//1.
|
|
// old name restored
|
|
QVERIFY(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_")));
|
|
// including contents
|
|
QVERIFY(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data")));
|
|
// new still exist
|
|
QVERIFY(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/newname_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data")));
|
|
// but is not on server: so remove it localy for the future comarison
|
|
fakeFolder.localModifier().remove(QStringLiteral("readonlyDirectory_PERM_M_/newname_PERM_CK_"));
|
|
|
|
//2.
|
|
// old removed
|
|
QVERIFY(!currentLocalState.find(QStringLiteral("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_")));
|
|
// but still on the server: the rename causing an error meant the deletes didn't execute
|
|
QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_")));
|
|
// new still there
|
|
QVERIFY(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/moved_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data")));
|
|
//but not on server
|
|
fakeFolder.localModifier().remove(QStringLiteral("readonlyDirectory_PERM_M_/moved_PERM_CK_"));
|
|
fakeFolder.remoteModifier().remove(QStringLiteral("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_"));
|
|
QVERIFY(fakeFolder.applyLocalModificationsAndSync());
|
|
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
|
|
|
|
//######################################################################
|
|
qInfo( "multiple restores of a file create different conflict files" );
|
|
|
|
fakeFolder.remoteModifier().insert(QStringLiteral("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data"));
|
|
applyPermissionsFromName(fakeFolder.remoteModifier());
|
|
QVERIFY(fakeFolder.applyLocalModificationsAndSync());
|
|
|
|
editReadOnly(QStringLiteral("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data"));
|
|
fakeFolder.localModifier().setContents(QStringLiteral("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data"), FileModifier::DefaultFileSize, 's');
|
|
//do the sync
|
|
applyPermissionsFromName(fakeFolder.remoteModifier());
|
|
QVERIFY(fakeFolder.applyLocalModificationsAndSync());
|
|
assertCsyncJournalOk(fakeFolder.syncJournal());
|
|
|
|
QThread::sleep(1); // make sure changes have different mtime
|
|
editReadOnly(QStringLiteral("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data"));
|
|
fakeFolder.localModifier().setContents(QStringLiteral("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data"), FileModifier::DefaultFileSize, 'd');
|
|
|
|
//do the sync
|
|
applyPermissionsFromName(fakeFolder.remoteModifier());
|
|
QVERIFY(fakeFolder.applyLocalModificationsAndSync());
|
|
assertCsyncJournalOk(fakeFolder.syncJournal());
|
|
|
|
// there should be two conflict files
|
|
currentLocalState = fakeFolder.currentLocalState();
|
|
int count = 0;
|
|
while (auto i = findConflict(currentLocalState, QStringLiteral("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data"))) {
|
|
QVERIFY((i->contentChar == 's') || (i->contentChar == 'd'));
|
|
fakeFolder.localModifier().remove(i->path());
|
|
QVERIFY(fakeFolder.applyLocalModificationsWithoutSync());
|
|
currentLocalState = fakeFolder.currentLocalState();
|
|
count++;
|
|
}
|
|
QCOMPARE(count, 2);
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
}
|
|
|
|
static void setAllPerm(FileInfo *fi, RemotePermissions perm)
|
|
{
|
|
fi->permissions = perm;
|
|
for (auto &subFi : fi->children)
|
|
setAllPerm(&subFi, perm);
|
|
}
|
|
|
|
// What happens if the source can't be moved or the target can't be created?
|
|
void testForbiddenMoves()
|
|
{
|
|
QFETCH_GLOBAL(Vfs::Mode, vfsMode);
|
|
QFETCH_GLOBAL(bool, filesAreDehydrated);
|
|
|
|
if (vfsMode == Vfs::Mode::WindowsCfApi) {
|
|
QSKIP("Known issue: the winvfs plug-in fails to set file permissions");
|
|
}
|
|
|
|
FakeFolder fakeFolder(FileInfo(), vfsMode, filesAreDehydrated);
|
|
|
|
// Some of this test depends on the order of discovery. With threading
|
|
// that order becomes effectively random, but we want to make sure to test
|
|
// all cases and thus disable threading.
|
|
auto syncOpts = fakeFolder.syncEngine().syncOptions();
|
|
syncOpts._parallelNetworkJobs = [] { return 1; };
|
|
fakeFolder.syncEngine().setSyncOptions(syncOpts);
|
|
|
|
auto &lm = fakeFolder.localModifier();
|
|
auto &rm = fakeFolder.remoteModifier();
|
|
rm.mkdir(QStringLiteral("allowed"));
|
|
rm.mkdir(QStringLiteral("norename"));
|
|
rm.mkdir(QStringLiteral("nomove"));
|
|
rm.mkdir(QStringLiteral("nocreatefile"));
|
|
rm.mkdir(QStringLiteral("nocreatedir"));
|
|
rm.mkdir(QStringLiteral("zallowed")); // order of discovery matters
|
|
|
|
rm.mkdir(QStringLiteral("allowed/sub"));
|
|
rm.mkdir(QStringLiteral("allowed/sub2"));
|
|
rm.insert(QStringLiteral("allowed/file"));
|
|
rm.insert(QStringLiteral("allowed/sub/file"));
|
|
rm.insert(QStringLiteral("allowed/sub2/file"));
|
|
rm.mkdir(QStringLiteral("norename/sub"));
|
|
rm.insert(QStringLiteral("norename/file"));
|
|
rm.insert(QStringLiteral("norename/sub/file"));
|
|
rm.mkdir(QStringLiteral("nomove/sub"));
|
|
rm.insert(QStringLiteral("nomove/file"));
|
|
rm.insert(QStringLiteral("nomove/sub/file"));
|
|
rm.mkdir(QStringLiteral("zallowed/sub"));
|
|
rm.mkdir(QStringLiteral("zallowed/sub2"));
|
|
rm.insert(QStringLiteral("zallowed/file"));
|
|
rm.insert(QStringLiteral("zallowed/sub/file"));
|
|
rm.insert(QStringLiteral("zallowed/sub2/file"));
|
|
|
|
setAllPerm(rm.find(QStringLiteral("norename")), RemotePermissions::fromServerString(QStringLiteral("WDVCK")));
|
|
setAllPerm(rm.find(QStringLiteral("nomove")), RemotePermissions::fromServerString(QStringLiteral("WDNCK")));
|
|
setAllPerm(rm.find(QStringLiteral("nocreatefile")), RemotePermissions::fromServerString(QStringLiteral("WDNVK")));
|
|
setAllPerm(rm.find(QStringLiteral("nocreatedir")), RemotePermissions::fromServerString(QStringLiteral("WDNVC")));
|
|
|
|
QVERIFY(fakeFolder.applyLocalModificationsAndSync());
|
|
|
|
// Renaming errors
|
|
lm.rename(QStringLiteral("norename/file"), QStringLiteral("norename/file_renamed"));
|
|
lm.rename(QStringLiteral("norename/sub"), QStringLiteral("norename/sub_renamed"));
|
|
// Moving errors
|
|
lm.rename(QStringLiteral("nomove/file"), QStringLiteral("allowed/file_moved"));
|
|
lm.rename(QStringLiteral("nomove/sub"), QStringLiteral("allowed/sub_moved"));
|
|
// Createfile errors
|
|
lm.rename(QStringLiteral("allowed/file"), QStringLiteral("nocreatefile/file"));
|
|
lm.rename(QStringLiteral("zallowed/file"), QStringLiteral("nocreatefile/zfile"));
|
|
lm.rename(QStringLiteral("allowed/sub"), QStringLiteral("nocreatefile/sub")); // TODO: probably forbidden because it contains file children?
|
|
// Createdir errors
|
|
lm.rename(QStringLiteral("allowed/sub2"), QStringLiteral("nocreatedir/sub2"));
|
|
lm.rename(QStringLiteral("zallowed/sub2"), QStringLiteral("nocreatedir/zsub2"));
|
|
|
|
// also hook into discovery!!
|
|
SyncFileItemSet discovery;
|
|
connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToPropagate, this, [&discovery](auto v) { discovery = v; });
|
|
ItemCompletedSpy completeSpy(fakeFolder);
|
|
QVERIFY(!fakeFolder.applyLocalModificationsAndSync());
|
|
|
|
// if renaming doesn't work, just delete+create
|
|
QVERIFY(itemInstruction(completeSpy, QStringLiteral("norename/file"), CSYNC_INSTRUCTION_REMOVE));
|
|
QVERIFY(itemInstruction(completeSpy, QStringLiteral("norename/sub"), CSYNC_INSTRUCTION_REMOVE));
|
|
QVERIFY(discoveryInstruction(discovery, QStringLiteral("norename/sub"), CSYNC_INSTRUCTION_REMOVE));
|
|
QVERIFY(itemInstruction(completeSpy, QStringLiteral("norename/file_renamed"), CSYNC_INSTRUCTION_NEW));
|
|
QVERIFY(itemInstruction(completeSpy, QStringLiteral("norename/sub_renamed"), CSYNC_INSTRUCTION_NEW));
|
|
// the contents can _move_
|
|
QVERIFY(itemInstruction(completeSpy, QStringLiteral("norename/sub_renamed/file"), CSYNC_INSTRUCTION_RENAME));
|
|
|
|
// simiilarly forbidding moves becomes delete+create
|
|
QVERIFY(itemInstruction(completeSpy, QStringLiteral("nomove/file"), CSYNC_INSTRUCTION_REMOVE));
|
|
QVERIFY(itemInstruction(completeSpy, QStringLiteral("nomove/sub"), CSYNC_INSTRUCTION_REMOVE));
|
|
QVERIFY(discoveryInstruction(discovery, QStringLiteral("nomove/sub"), CSYNC_INSTRUCTION_REMOVE));
|
|
// nomove/sub/file is removed as part of the dir
|
|
QVERIFY(itemInstruction(completeSpy, QStringLiteral("allowed/file_moved"), CSYNC_INSTRUCTION_NEW));
|
|
QVERIFY(itemInstruction(completeSpy, QStringLiteral("allowed/sub_moved"), CSYNC_INSTRUCTION_NEW));
|
|
QVERIFY(itemInstruction(completeSpy, QStringLiteral("allowed/sub_moved/file"), CSYNC_INSTRUCTION_NEW));
|
|
|
|
// when moving to an invalid target, the targets should be an error
|
|
QVERIFY(itemInstruction(completeSpy, QStringLiteral("nocreatefile/file"), CSYNC_INSTRUCTION_ERROR));
|
|
QVERIFY(itemInstruction(completeSpy, QStringLiteral("nocreatefile/zfile"), CSYNC_INSTRUCTION_ERROR));
|
|
QVERIFY(itemInstruction(completeSpy, QStringLiteral("nocreatefile/sub"), CSYNC_INSTRUCTION_RENAME)); // TODO: What does a real server say?
|
|
QVERIFY(itemInstruction(completeSpy, QStringLiteral("nocreatedir/sub2"), CSYNC_INSTRUCTION_ERROR));
|
|
QVERIFY(itemInstruction(completeSpy, QStringLiteral("nocreatedir/zsub2"), CSYNC_INSTRUCTION_ERROR));
|
|
|
|
// and the sources of the invalid moves should be restored, not deleted
|
|
// (depending on the order of discovery a follow-up sync is needed)
|
|
QVERIFY(completeSpy.findItem(QStringLiteral("allowed/file")).isNull());
|
|
QVERIFY(completeSpy.findItem(QStringLiteral("allowed/sub2")).isNull());
|
|
QVERIFY(itemInstruction(completeSpy, QStringLiteral("zallowed/file"), CSYNC_INSTRUCTION_NEW));
|
|
QVERIFY(itemInstruction(completeSpy, QStringLiteral("zallowed/sub2"), CSYNC_INSTRUCTION_NEW));
|
|
QVERIFY(itemInstruction(completeSpy, QStringLiteral("zallowed/sub2/file"), CSYNC_INSTRUCTION_NEW));
|
|
QCOMPARE(fakeFolder.syncEngine().isAnotherSyncNeeded(), true);
|
|
|
|
// A follow-up sync will restore allowed/file and allowed/sub2 and maintain the nocreatedir/file errors
|
|
completeSpy.clear();
|
|
QCOMPARE(fakeFolder.syncJournal().wipeErrorBlacklist(), 4);
|
|
QVERIFY(!fakeFolder.applyLocalModificationsAndSync());
|
|
|
|
QVERIFY(itemInstruction(completeSpy, QStringLiteral("nocreatefile/file"), CSYNC_INSTRUCTION_ERROR));
|
|
QVERIFY(itemInstruction(completeSpy, QStringLiteral("nocreatefile/zfile"), CSYNC_INSTRUCTION_ERROR));
|
|
QVERIFY(itemInstruction(completeSpy, QStringLiteral("nocreatedir/sub2"), CSYNC_INSTRUCTION_ERROR));
|
|
QVERIFY(itemInstruction(completeSpy, QStringLiteral("nocreatedir/zsub2"), CSYNC_INSTRUCTION_ERROR));
|
|
|
|
QVERIFY(itemInstruction(completeSpy, QStringLiteral("allowed/file"), CSYNC_INSTRUCTION_NEW));
|
|
QVERIFY(itemInstruction(completeSpy, QStringLiteral("allowed/sub2"), CSYNC_INSTRUCTION_NEW));
|
|
QVERIFY(itemInstruction(completeSpy, QStringLiteral("allowed/sub2/file"), CSYNC_INSTRUCTION_NEW));
|
|
|
|
auto cls = fakeFolder.currentLocalState();
|
|
QVERIFY(cls.find(QStringLiteral("allowed/file")));
|
|
QVERIFY(cls.find(QStringLiteral("allowed/sub2")));
|
|
QVERIFY(cls.find(QStringLiteral("zallowed/file")));
|
|
QVERIFY(cls.find(QStringLiteral("zallowed/sub2")));
|
|
QVERIFY(cls.find(QStringLiteral("zallowed/sub2/file")));
|
|
}
|
|
|
|
// Test for issue #7293
|
|
void testAllowedMoveForbiddenDelete() {
|
|
QFETCH_GLOBAL(Vfs::Mode, vfsMode);
|
|
QFETCH_GLOBAL(bool, filesAreDehydrated);
|
|
|
|
if (vfsMode == Vfs::Mode::WindowsCfApi) {
|
|
QSKIP("Known issue: the winvfs plug-in fails to set file permissions");
|
|
}
|
|
|
|
FakeFolder fakeFolder(FileInfo(), vfsMode, filesAreDehydrated);
|
|
|
|
// Some of this test depends on the order of discovery. With threading
|
|
// that order becomes effectively random, but we want to make sure to test
|
|
// all cases and thus disable threading.
|
|
auto syncOpts = fakeFolder.syncEngine().syncOptions();
|
|
syncOpts._parallelNetworkJobs = [] { return 1; };
|
|
fakeFolder.syncEngine().setSyncOptions(syncOpts);
|
|
|
|
auto &lm = fakeFolder.localModifier();
|
|
auto &rm = fakeFolder.remoteModifier();
|
|
rm.mkdir(QStringLiteral("changeonly"));
|
|
rm.mkdir(QStringLiteral("changeonly/sub1"));
|
|
rm.insert(QStringLiteral("changeonly/sub1/file1"));
|
|
rm.insert(QStringLiteral("changeonly/sub1/filetorname1a"));
|
|
rm.insert(QStringLiteral("changeonly/sub1/filetorname1z"));
|
|
rm.mkdir(QStringLiteral("changeonly/sub2"));
|
|
rm.insert(QStringLiteral("changeonly/sub2/file2"));
|
|
rm.insert(QStringLiteral("changeonly/sub2/filetorname2a"));
|
|
rm.insert(QStringLiteral("changeonly/sub2/filetorname2z"));
|
|
|
|
setAllPerm(rm.find(QStringLiteral("changeonly")), RemotePermissions::fromServerString(QStringLiteral("NSV")));
|
|
|
|
QVERIFY(fakeFolder.applyLocalModificationsAndSync());
|
|
|
|
lm.rename(QStringLiteral("changeonly/sub1/filetorname1a"), QStringLiteral("changeonly/sub1/aaa1_renamed"));
|
|
lm.rename(QStringLiteral("changeonly/sub1/filetorname1z"), QStringLiteral("changeonly/sub1/zzz1_renamed"));
|
|
|
|
lm.rename(QStringLiteral("changeonly/sub2/filetorname2a"), QStringLiteral("changeonly/sub2/aaa2_renamed"));
|
|
lm.rename(QStringLiteral("changeonly/sub2/filetorname2z"), QStringLiteral("changeonly/sub2/zzz2_renamed"));
|
|
|
|
lm.rename(QStringLiteral("changeonly/sub1"), QStringLiteral("changeonly/aaa"));
|
|
lm.rename(QStringLiteral("changeonly/sub2"), QStringLiteral("changeonly/zzz"));
|
|
|
|
|
|
QVERIFY(fakeFolder.applyLocalModificationsAndSync());
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
}
|
|
};
|
|
|
|
QTEST_GUILESS_MAIN(TestPermissions)
|
|
#include "testpermissions.moc"
|