testfolderman.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. /*
  2. * This software is in the public domain, furnished "as is", without technical
  3. * support, and with no warranty, express or implied, as to its usefulness for
  4. * any purpose.
  5. *
  6. */
  7. #include <qglobal.h>
  8. #include <QTemporaryDir>
  9. #include <QtTest>
  10. #include "QtTest/qtestcase.h"
  11. #include "common/utility.h"
  12. #include "folderman.h"
  13. #include "account.h"
  14. #include "accountstate.h"
  15. #include <accountmanager.h>
  16. #include "configfile.h"
  17. #include "syncenginetestutils.h"
  18. #include "testhelper.h"
  19. using namespace OCC;
  20. static QByteArray fake400Response = R"(
  21. {"ocs":{"meta":{"status":"failure","statuscode":400,"message":"Parameter is incorrect.\n"},"data":[]}}
  22. )";
  23. bool itemDidCompleteSuccessfully(const ItemCompletedSpy &spy, const QString &path)
  24. {
  25. if (auto item = spy.findItem(path)) {
  26. return item->_status == SyncFileItem::Success;
  27. }
  28. return false;
  29. }
  30. class TestFolderMan: public QObject
  31. {
  32. Q_OBJECT
  33. FolderMan _fm;
  34. signals:
  35. void incomingShareDeleted();
  36. private slots:
  37. void testDeleteEncryptedFiles()
  38. {
  39. FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
  40. QCOMPARE(fakeFolder.currentLocalState().children.count(), 4);
  41. ItemCompletedSpy completeSpy(fakeFolder);
  42. fakeFolder.localModifier().mkdir("encrypted");
  43. fakeFolder.localModifier().setE2EE("encrypted", true);
  44. fakeFolder.remoteModifier().mkdir("encrypted");
  45. fakeFolder.remoteModifier().setE2EE("encrypted", true);
  46. const auto fakeFileInfo = fakeFolder.remoteModifier().find("encrypted");
  47. QVERIFY(fakeFileInfo);
  48. QVERIFY(fakeFileInfo->isEncrypted);
  49. QCOMPARE(fakeFolder.currentLocalState().children.count(), 5);
  50. const auto fakeFileId = fakeFileInfo->fileId;
  51. const auto fakeQnam = new FakeQNAM({});
  52. // Let's avoid the null filename assert in the default FakeQNAM request creation
  53. const auto fakeQnamOverride = [this, fakeFileId](const QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *device) {
  54. Q_UNUSED(device)
  55. QNetworkReply *reply = nullptr;
  56. const auto reqUrl = req.url();
  57. const auto reqRawPath = reqUrl.path();
  58. const auto reqPath = reqRawPath.startsWith("/owncloud/") ? reqRawPath.mid(10) : reqRawPath;
  59. if (reqPath.startsWith(QStringLiteral("ocs/v2.php/apps/end_to_end_encryption/api/v1/meta-data/"))) {
  60. const auto splitUrlPath = reqPath.split('/');
  61. const auto fileId = splitUrlPath.last();
  62. const QUrlQuery urlQuery(req.url());
  63. const auto formatParam = urlQuery.queryItemValue(QStringLiteral("format"));
  64. if(fileId == fakeFileId && formatParam == QStringLiteral("json")) {
  65. reply = new FakePayloadReply(op, req, QJsonDocument().toJson(), this);
  66. } else {
  67. reply = new FakeErrorReply(op, req, this, 400, fake400Response);
  68. }
  69. }
  70. return reply;
  71. };
  72. fakeFolder.setServerOverride(fakeQnamOverride);
  73. fakeQnam->setOverride(fakeQnamOverride);
  74. const auto account = Account::create();
  75. const auto capabilities = QVariantMap {
  76. {QStringLiteral("end-to-end-encryption"), QVariantMap {
  77. {QStringLiteral("enabled"), true},
  78. {QStringLiteral("api-version"), QString::number(2.0)},
  79. }},
  80. };
  81. account->setCapabilities(capabilities);
  82. account->setCredentials(new FakeCredentials{fakeQnam});
  83. account->setUrl(QUrl(("owncloud://somehost/owncloud")));
  84. const auto accountState = new FakeAccountState(account);
  85. QVERIFY(accountState->isConnected());
  86. QVERIFY(fakeFolder.syncOnce());
  87. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  88. auto folderDef = folderDefinition(fakeFolder.localPath());
  89. folderDef.targetPath = "";
  90. const auto folder = FolderMan::instance()->addFolder(accountState, folderDef);
  91. QVERIFY(folder);
  92. qRegisterMetaType<OCC::SyncResult>("SyncResult");
  93. QSignalSpy folderSyncDone(folder, &Folder::syncFinished);
  94. QDir dir(folder->path() + QStringLiteral("encrypted"));
  95. QVERIFY(dir.exists());
  96. QVERIFY(fakeFolder.remoteModifier().find("encrypted"));
  97. QVERIFY(fakeFolder.currentLocalState().find("encrypted"));
  98. QCOMPARE(fakeFolder.currentLocalState().children.count(), 5);
  99. // Rather than go through the pain of trying to replicate the E2EE response from
  100. // the server, let's just manually set the encryption bool in the folder journal
  101. SyncJournalFileRecord rec;
  102. QVERIFY(folder->journalDb()->getFileRecord(QStringLiteral("encrypted"), &rec));
  103. rec._e2eEncryptionStatus = SyncJournalFileRecord::EncryptionStatus::EncryptedMigratedV1_2;
  104. rec._path = QStringLiteral("encrypted").toUtf8();
  105. rec._type = CSyncEnums::ItemTypeDirectory;
  106. QVERIFY(folder->journalDb()->setFileRecord(rec));
  107. SyncJournalFileRecord updatedRec;
  108. QVERIFY(folder->journalDb()->getFileRecord(QStringLiteral("encrypted"), &updatedRec));
  109. QVERIFY(updatedRec.isE2eEncrypted());
  110. QVERIFY(updatedRec.isDirectory());
  111. FolderMan::instance()->removeE2eFiles(account);
  112. if (folderSyncDone.isEmpty()) {
  113. QVERIFY(folderSyncDone.wait());
  114. }
  115. QVERIFY(fakeFolder.currentRemoteState().find("encrypted"));
  116. QVERIFY(!fakeFolder.currentLocalState().find("encrypted"));
  117. QCOMPARE(fakeFolder.currentLocalState().children.count(), 4);
  118. }
  119. void testLeaveShare()
  120. {
  121. QTemporaryDir dir;
  122. ConfigFile::setConfDir(dir.path()); // we don't want to pollute the user's config file
  123. constexpr auto firstSharePath = "A/sharedwithme_A.txt";
  124. constexpr auto secondSharePath = "A/B/sharedwithme_B.data";
  125. QScopedPointer<FakeQNAM> fakeQnam(new FakeQNAM({}));
  126. OCC::AccountPtr account = OCC::Account::create();
  127. account->setCredentials(new FakeCredentials{fakeQnam.data()});
  128. account->setUrl(QUrl(("http://example.de")));
  129. OCC::AccountManager::instance()->addAccount(account);
  130. FakeFolder fakeFolder{FileInfo{}};
  131. fakeFolder.remoteModifier().mkdir("A");
  132. fakeFolder.remoteModifier().insert(firstSharePath, 100);
  133. const auto firstShare = fakeFolder.remoteModifier().find(firstSharePath);
  134. QVERIFY(firstShare);
  135. firstShare->permissions.setPermission(OCC::RemotePermissions::IsShared);
  136. fakeFolder.remoteModifier().mkdir("A/B");
  137. fakeFolder.remoteModifier().insert(secondSharePath, 100);
  138. const auto secondShare = fakeFolder.remoteModifier().find(secondSharePath);
  139. QVERIFY(secondShare);
  140. secondShare->permissions.setPermission(OCC::RemotePermissions::IsShared);
  141. FolderMan *folderman = FolderMan::instance();
  142. QCOMPARE(folderman, &_fm);
  143. OCC::AccountState *accountState = OCC::AccountManager::instance()->accounts().first().data();
  144. const auto folder = folderman->addFolder(accountState, folderDefinition(fakeFolder.localPath()));
  145. QVERIFY(folder);
  146. auto realFolder = FolderMan::instance()->folderForPath(fakeFolder.localPath());
  147. QVERIFY(realFolder);
  148. QVERIFY(fakeFolder.syncOnce());
  149. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  150. fakeQnam->setOverride([this, accountState, &fakeFolder](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *device) {
  151. Q_UNUSED(device);
  152. QNetworkReply *reply = nullptr;
  153. if (op != QNetworkAccessManager::DeleteOperation) {
  154. reply = new FakeErrorReply(op, req, this, 405);
  155. return reply;
  156. }
  157. if (req.url().path().isEmpty()) {
  158. reply = new FakeErrorReply(op, req, this, 404);
  159. return reply;
  160. }
  161. const auto filePathRelative = req.url().path().remove(accountState->account()->davPath());
  162. const auto foundFileInRemoteFolder = fakeFolder.remoteModifier().find(filePathRelative);
  163. if (filePathRelative.isEmpty() || !foundFileInRemoteFolder) {
  164. reply = new FakeErrorReply(op, req, this, 404);
  165. return reply;
  166. }
  167. fakeFolder.remoteModifier().remove(filePathRelative);
  168. reply = new FakePayloadReply(op, req, {}, nullptr);
  169. emit incomingShareDeleted();
  170. return reply;
  171. });
  172. QSignalSpy incomingShareDeletedSignal(this, &TestFolderMan::incomingShareDeleted);
  173. // verify first share gets deleted
  174. folderman->leaveShare(fakeFolder.localPath() + firstSharePath);
  175. QCOMPARE(incomingShareDeletedSignal.count(), 1);
  176. QVERIFY(!fakeFolder.remoteModifier().find(firstSharePath));
  177. QVERIFY(fakeFolder.syncOnce());
  178. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  179. // verify no share gets deleted
  180. folderman->leaveShare(fakeFolder.localPath() + "A/B/notsharedwithme_B.data");
  181. QCOMPARE(incomingShareDeletedSignal.count(), 1);
  182. QVERIFY(fakeFolder.remoteModifier().find("A/B/sharedwithme_B.data"));
  183. QVERIFY(fakeFolder.syncOnce());
  184. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  185. // verify second share gets deleted
  186. folderman->leaveShare(fakeFolder.localPath() + secondSharePath);
  187. QCOMPARE(incomingShareDeletedSignal.count(), 2);
  188. QVERIFY(!fakeFolder.remoteModifier().find(secondSharePath));
  189. QVERIFY(fakeFolder.syncOnce());
  190. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  191. OCC::AccountManager::instance()->deleteAccount(accountState);
  192. }
  193. void testCheckPathValidityForNewFolder()
  194. {
  195. #ifdef Q_OS_WIN
  196. Utility::NtfsPermissionLookupRAII ntfs_perm;
  197. #endif
  198. QTemporaryDir dir;
  199. ConfigFile::setConfDir(dir.path()); // we don't want to pollute the user's config file
  200. QVERIFY(dir.isValid());
  201. QDir dir2(dir.path());
  202. QVERIFY(dir2.mkpath("sub/ownCloud1/folder/f"));
  203. QVERIFY(dir2.mkpath("ownCloud2"));
  204. QVERIFY(dir2.mkpath("sub/free"));
  205. QVERIFY(dir2.mkpath("free2/sub"));
  206. {
  207. QFile f(dir.path() + "/sub/file.txt");
  208. f.open(QFile::WriteOnly);
  209. f.write("hello");
  210. }
  211. QString dirPath = dir2.canonicalPath();
  212. AccountPtr account = Account::create();
  213. QUrl url("http://example.de");
  214. auto *cred = new HttpCredentialsTest("testuser", "secret");
  215. account->setCredentials(cred);
  216. account->setUrl( url );
  217. AccountStatePtr newAccountState(new AccountState(account));
  218. FolderMan *folderman = FolderMan::instance();
  219. QCOMPARE(folderman, &_fm);
  220. QVERIFY(folderman->addFolder(newAccountState.data(), folderDefinition(dirPath + "/sub/ownCloud1")));
  221. QVERIFY(folderman->addFolder(newAccountState.data(), folderDefinition(dirPath + "/ownCloud2")));
  222. const auto folderList = folderman->map();
  223. for (const auto &folder : folderList) {
  224. QVERIFY(!folder->isSyncRunning());
  225. }
  226. // those should be allowed
  227. // QString FolderMan::checkPathValidityForNewFolder(const QString& path, const QUrl &serverUrl, bool forNewDirectory).second
  228. QCOMPARE(folderman->checkPathValidityForNewFolder(dirPath + "/sub/free").second, QString());
  229. QCOMPARE(folderman->checkPathValidityForNewFolder(dirPath + "/free2/").second, QString());
  230. // Not an existing directory -> Ok
  231. QCOMPARE(folderman->checkPathValidityForNewFolder(dirPath + "/sub/bliblablu").second, QString());
  232. QCOMPARE(folderman->checkPathValidityForNewFolder(dirPath + "/sub/free/bliblablu").second, QString());
  233. // QCOMPARE(folderman->checkPathValidityForNewFolder(dirPath + "/sub/bliblablu/some/more").second, QString());
  234. // A file -> Error
  235. QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/sub/file.txt").second.isNull());
  236. // There are folders configured in those folders, url needs to be taken into account: -> ERROR
  237. QUrl url2(url);
  238. const QString user = account->credentials()->user();
  239. url2.setUserName(user);
  240. // The following both fail because they refer to the same account (user and url)
  241. QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/sub/ownCloud1", url2).second.isNull());
  242. QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/ownCloud2/", url2).second.isNull());
  243. // Now it will work because the account is different
  244. QUrl url3("http://anotherexample.org");
  245. url3.setUserName("dummy");
  246. QCOMPARE(folderman->checkPathValidityForNewFolder(dirPath + "/sub/ownCloud1", url3).second, QString());
  247. QCOMPARE(folderman->checkPathValidityForNewFolder(dirPath + "/ownCloud2/", url3).second, QString());
  248. QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath).second.isNull());
  249. QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/sub/ownCloud1/folder").second.isNull());
  250. QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/sub/ownCloud1/folder/f").second.isNull());
  251. #ifndef Q_OS_WIN // no links on windows, no permissions
  252. // make a bunch of links
  253. QVERIFY(QFile::link(dirPath + "/sub/free", dirPath + "/link1"));
  254. QVERIFY(QFile::link(dirPath + "/sub", dirPath + "/link2"));
  255. QVERIFY(QFile::link(dirPath + "/sub/ownCloud1", dirPath + "/link3"));
  256. QVERIFY(QFile::link(dirPath + "/sub/ownCloud1/folder", dirPath + "/link4"));
  257. // Ok
  258. QVERIFY(folderman->checkPathValidityForNewFolder(dirPath + "/link1").second.isNull());
  259. QVERIFY(folderman->checkPathValidityForNewFolder(dirPath + "/link2/free").second.isNull());
  260. // Not Ok
  261. QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/link2").second.isNull());
  262. // link 3 points to an existing sync folder. To make it fail, the account must be the same
  263. QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/link3", url2).second.isNull());
  264. // while with a different account, this is fine
  265. QCOMPARE(folderman->checkPathValidityForNewFolder(dirPath + "/link3", url3).second, QString());
  266. QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/link4").second.isNull());
  267. QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/link3/folder").second.isNull());
  268. // test some non existing sub path (error)
  269. QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/sub/ownCloud1/some/sub/path").second.isNull());
  270. QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/ownCloud2/blublu").second.isNull());
  271. QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/sub/ownCloud1/folder/g/h").second.isNull());
  272. QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/link3/folder/neu_folder").second.isNull());
  273. // Subfolder of links
  274. QVERIFY(folderman->checkPathValidityForNewFolder(dirPath + "/link1/subfolder").second.isNull());
  275. QVERIFY(folderman->checkPathValidityForNewFolder(dirPath + "/link2/free/subfolder").second.isNull());
  276. // Should not have the rights
  277. QVERIFY(!folderman->checkPathValidityForNewFolder("/").second.isNull());
  278. QVERIFY(!folderman->checkPathValidityForNewFolder("/usr/bin/somefolder").second.isNull());
  279. #endif
  280. #ifdef Q_OS_WIN // drive-letter tests
  281. if (!QFileInfo("v:/").exists()) {
  282. QVERIFY(!folderman->checkPathValidityForNewFolder("v:").second.isNull());
  283. QVERIFY(!folderman->checkPathValidityForNewFolder("v:/").second.isNull());
  284. QVERIFY(!folderman->checkPathValidityForNewFolder("v:/foo").second.isNull());
  285. }
  286. if (QFileInfo("c:/").isWritable()) {
  287. QVERIFY(folderman->checkPathValidityForNewFolder("c:").second.isNull());
  288. QVERIFY(folderman->checkPathValidityForNewFolder("c:/").second.isNull());
  289. QVERIFY(folderman->checkPathValidityForNewFolder("c:/foo").second.isNull());
  290. }
  291. #endif
  292. // Invalid paths
  293. QVERIFY(!folderman->checkPathValidityForNewFolder("").second.isNull());
  294. // REMOVE ownCloud2 from the filesystem, but keep a folder sync'ed to it.
  295. QDir(dirPath + "/ownCloud2/").removeRecursively();
  296. QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/ownCloud2/blublu").second.isNull());
  297. QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/ownCloud2/sub/subsub/sub").second.isNull());
  298. }
  299. void testFindGoodPathForNewSyncFolder()
  300. {
  301. // SETUP
  302. QTemporaryDir dir;
  303. ConfigFile::setConfDir(dir.path()); // we don't want to pollute the user's config file
  304. QVERIFY(dir.isValid());
  305. QDir dir2(dir.path());
  306. QVERIFY(dir2.mkpath("sub/ownCloud1/folder/f"));
  307. QVERIFY(dir2.mkpath("ownCloud"));
  308. QVERIFY(dir2.mkpath("ownCloud2"));
  309. QVERIFY(dir2.mkpath("ownCloud2/foo"));
  310. QVERIFY(dir2.mkpath("sub/free"));
  311. QVERIFY(dir2.mkpath("free2/sub"));
  312. QString dirPath = dir2.canonicalPath();
  313. AccountPtr account = Account::create();
  314. QUrl url("http://example.de");
  315. auto *cred = new HttpCredentialsTest("testuser", "secret");
  316. account->setCredentials(cred);
  317. account->setUrl( url );
  318. url.setUserName(cred->user());
  319. AccountStatePtr newAccountState(new AccountState(account));
  320. FolderMan *folderman = FolderMan::instance();
  321. QCOMPARE(folderman, &_fm);
  322. QVERIFY(folderman->addFolder(newAccountState.data(), folderDefinition(dirPath + "/sub/ownCloud/")));
  323. QVERIFY(folderman->addFolder(newAccountState.data(), folderDefinition(dirPath + "/ownCloud2/")));
  324. // TEST
  325. QCOMPARE(folderman->findGoodPathForNewSyncFolder(dirPath + "/oc", url, FolderMan::GoodPathStrategy::AllowOnlyNewPath),
  326. QString(dirPath + "/oc"));
  327. QCOMPARE(folderman->findGoodPathForNewSyncFolder(dirPath + "/ownCloud", url, FolderMan::GoodPathStrategy::AllowOnlyNewPath),
  328. QString(dirPath + "/ownCloud3"));
  329. QCOMPARE(folderman->findGoodPathForNewSyncFolder(dirPath + "/ownCloud2", url, FolderMan::GoodPathStrategy::AllowOnlyNewPath),
  330. QString(dirPath + "/ownCloud22"));
  331. QCOMPARE(folderman->findGoodPathForNewSyncFolder(dirPath + "/ownCloud2/foo", url, FolderMan::GoodPathStrategy::AllowOnlyNewPath),
  332. QString(dirPath + "/ownCloud2/foo"));
  333. QCOMPARE(folderman->findGoodPathForNewSyncFolder(dirPath + "/ownCloud2/bar", url, FolderMan::GoodPathStrategy::AllowOnlyNewPath),
  334. QString(dirPath + "/ownCloud2/bar"));
  335. QCOMPARE(folderman->findGoodPathForNewSyncFolder(dirPath + "/sub", url, FolderMan::GoodPathStrategy::AllowOnlyNewPath),
  336. QString(dirPath + "/sub2"));
  337. // REMOVE ownCloud2 from the filesystem, but keep a folder sync'ed to it.
  338. // We should still not suggest this folder as a new folder.
  339. QDir(dirPath + "/ownCloud2/").removeRecursively();
  340. QCOMPARE(folderman->findGoodPathForNewSyncFolder(dirPath + "/ownCloud", url, FolderMan::GoodPathStrategy::AllowOnlyNewPath),
  341. QString(dirPath + "/ownCloud3"));
  342. QCOMPARE(folderman->findGoodPathForNewSyncFolder(dirPath + "/ownCloud2", url, FolderMan::GoodPathStrategy::AllowOnlyNewPath),
  343. QString(dirPath + "/ownCloud22"));
  344. }
  345. };
  346. QTEST_GUILESS_MAIN(TestFolderMan)
  347. #include "testfolderman.moc"