| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487 |
- /*
- * 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 <account.h>
- #include <accountstate.h>
- #include <accountmanager.h>
- #include <common/vfs.h>
- #include <common/shellextensionutils.h>
- #include "config.h"
- #include <folderman.h>
- #include <libsync/vfs/cfapi/shellext/configvfscfapishellext.h>
- #include <ocssharejob.h>
- #include <shellextensionsserver.h>
- #include <syncengine.h>
- #include "syncenginetestutils.h"
- #include "testhelper.h"
- #include <vfs/cfapi/shellext/customstateprovideripc.h>
- #include <vfs/cfapi/shellext/thumbnailprovideripc.h>
- #include <QtTest>
- #include <QImage>
- #include <QPainter>
- namespace {
- static constexpr auto roootFolderName = "A";
- static constexpr auto imagesFolderName = "photos";
- static constexpr auto filesFolderName = "files";
- static const QByteArray fakeNoSharesResponse = R"({"ocs":{"data":[],"meta":{"message":"OK","status":"ok","statuscode":200}}})";
- static const QByteArray fakeSharedFilesResponse = R"({"ocs":{"data":[{
- "attributes": null,
- "can_delete": true,
- "can_edit": true,
- "displayname_file_owner": "admin",
- "displayname_owner": "admin",
- "expiration": null,
- "file_parent": 2981,
- "file_source": 3538,
- "file_target": "/test_shared_file.txt",
- "has_preview": true,
- "hide_download": 0,
- "id": "36",
- "item_source": 3538,
- "item_type": "file",
- "label": null,
- "mail_send": 0,
- "mimetype": "text/plain",
- "note": "",
- "parent": null,
- "path": "A/files/test_shared_file.txt",
- "permissions": 19,
- "share_type": 0,
- "share_with": "newstandard",
- "share_with_displayname": "newstandard",
- "share_with_displayname_unique": "newstandard",
- "status": {
- "clearAt": null,
- "icon": null,
- "message": null,
- "status": "offline"
- },
- "stime": 1662995777,
- "storage": 2,
- "storage_id": "home::admin",
- "token": null,
- "uid_file_owner": "admin",
- "uid_owner": "admin"
- },
- {
- "attributes": null,
- "can_delete": true,
- "can_edit": true,
- "displayname_file_owner": "admin",
- "displayname_owner": "admin",
- "expiration": null,
- "file_parent": 2981,
- "file_source": 3538,
- "file_target": "/test_shared_and_locked_file.txt",
- "has_preview": true,
- "hide_download": 0,
- "id": "36",
- "item_source": 3538,
- "item_type": "file",
- "label": null,
- "mail_send": 0,
- "mimetype": "text/plain",
- "note": "",
- "parent": null,
- "path": "A/files/test_shared_and_locked_file.txt",
- "permissions": 19,
- "share_type": 0,
- "share_with": "newstandard",
- "share_with_displayname": "newstandard",
- "share_with_displayname_unique": "newstandard",
- "status": {
- "clearAt": null,
- "icon": null,
- "message": null,
- "status": "offline"
- },
- "stime": 1662995777,
- "storage": 2,
- "storage_id": "home::admin",
- "token": null,
- "uid_file_owner": "admin",
- "uid_owner": "admin"
- }
- ],
- "meta": {
- "message": "OK",
- "status": "ok",
- "statuscode": 200
- }
- }
- })";
- static constexpr auto shellExtensionServerOverrideIntervalMs = 1000LL * 2LL;
- }
- using namespace OCC;
- class TestCfApiShellExtensionsIPC : public QObject
- {
- Q_OBJECT
- FolderMan _fm;
- FakeFolder fakeFolder{FileInfo()};
- QScopedPointer<FakeQNAM> fakeQnam;
- OCC::AccountPtr account;
- OCC::AccountState* accountState;
- QScopedPointer<ShellExtensionsServer> _shellExtensionsServer;
- const QStringList dummmyImageNames = {
- { QString(QString(roootFolderName) + QLatin1Char('/') + QString(imagesFolderName) + QLatin1Char('/') + QStringLiteral("imageJpg.jpg")) },
- { QString(QString(roootFolderName) + QLatin1Char('/') + QString(imagesFolderName) + QLatin1Char('/') + QStringLiteral("imagePng.png")) },
- { QString(QString(roootFolderName) + QLatin1Char('/') + QString(imagesFolderName) + QLatin1Char('/') + QStringLiteral("imagePng.bmp")) }
- };
- QMap<QString, QByteArray> dummyImages;
- QString currentImage;
- struct FileStates
- {
- bool _isShared = false;
- bool _isLocked = false;
- };
- const QMap<QString, FileStates> dummyFileStates = {
- { QString(QString(roootFolderName) + QLatin1Char('/') + QString(filesFolderName) + QLatin1Char('/') + QStringLiteral("test_locked_file.txt")), { false, true } },
- { QString(QString(roootFolderName) + QLatin1Char('/') + QString(filesFolderName) + QLatin1Char('/') + QStringLiteral("test_shared_file.txt")), { true, false } },
- { QString(QString(roootFolderName) + QLatin1Char('/') + QString(filesFolderName) + QLatin1Char('/') + QStringLiteral("test_shared_and_locked_file.txt")), { true, true }},
- { QString(QString(roootFolderName) + QLatin1Char('/') + QString(filesFolderName) + QLatin1Char('/') + QStringLiteral("test_non_shared_and_non_locked_file.txt")), { false, false }}
- };
- public:
- static bool replyWithNoShares;
- private slots:
- void initTestCase()
- {
- VfsShellExtensions::ThumbnailProviderIpc::overrideServerName = VfsShellExtensions::serverNameForApplicationNameDefault();
- VfsShellExtensions::CustomStateProviderIpc::overrideServerName = VfsShellExtensions::serverNameForApplicationNameDefault();
- _shellExtensionsServer.reset(new ShellExtensionsServer);
- _shellExtensionsServer->setIsSharedInvalidationInterval(shellExtensionServerOverrideIntervalMs);
- for (const auto &dummyImageName : dummmyImageNames) {
- const auto extension = dummyImageName.split(".").last();
- const auto format = dummyImageName.endsWith("PNG", Qt::CaseInsensitive) ? QImage::Format_ARGB32 : QImage::Format_RGB32;
- QImage image(QSize(640, 480), format);
- QPainter painter(&image);
- painter.setBrush(QBrush(Qt::red));
- painter.fillRect(QRectF(0, 0, 640, 480), Qt::red);
- QByteArray byteArray;
- QBuffer buffer(&byteArray);
- buffer.open(QIODevice::WriteOnly);
- image.save(&buffer, extension.toStdString().c_str());
- dummyImages.insert(dummyImageName, byteArray);
- }
- fakeFolder.remoteModifier().mkdir(roootFolderName);
- fakeFolder.remoteModifier().mkdir(QString(roootFolderName) + QLatin1Char('/') + QString(filesFolderName));
- fakeFolder.remoteModifier().mkdir(QString(roootFolderName) + QLatin1Char('/') + QString(imagesFolderName));
- for (const auto &fileStateKey : dummyFileStates.keys()) {
- fakeFolder.remoteModifier().insert(fileStateKey, 256);
- }
- fakeQnam.reset(new FakeQNAM({}));
- account = OCC::Account::create();
- account->setCredentials(new FakeCredentials{fakeQnam.data()});
- account->setUrl(QUrl(("http://example.de")));
- accountState = new OCC::AccountState(account);
- OCC::AccountManager::instance()->addAccount(account);
- FolderMan *folderman = FolderMan::instance();
- QCOMPARE(folderman, &_fm);
- QVERIFY(folderman->addFolder(accountState, folderDefinition(fakeFolder.localPath())));
- fakeQnam->setOverride(
- [this](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *device) {
- Q_UNUSED(device);
- QNetworkReply *reply = nullptr;
- const auto path = req.url().path();
- if (path.endsWith(OCC::OcsShareJob::_pathForSharesRequest)) {
- const auto jsonReply = TestCfApiShellExtensionsIPC::replyWithNoShares ? fakeNoSharesResponse : fakeSharedFilesResponse;
- TestCfApiShellExtensionsIPC::replyWithNoShares = false;
- auto fakePayloadReply = new FakePayloadReply(op, req, jsonReply, nullptr);
- QMap<QNetworkRequest::KnownHeaders, QByteArray> additionalHeaders = {
- {QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"}};
- fakePayloadReply->_additionalHeaders = additionalHeaders;
- reply = fakePayloadReply;
- } else if (path.endsWith(ShellExtensionsServer::getFetchThumbnailPath())) {
- const auto urlQuery = QUrlQuery(req.url());
- const auto fileId = urlQuery.queryItemValue(QStringLiteral("fileId"));
- const auto x = urlQuery.queryItemValue(QStringLiteral("x")).toInt();
- const auto y = urlQuery.queryItemValue(QStringLiteral("y")).toInt();
- if (fileId.isEmpty() || x <= 0 || y <= 0) {
- reply = new FakePayloadReply(op, req, {}, nullptr);
- } else {
- const auto foundImageIt = dummyImages.find(currentImage);
- QByteArray byteArray;
- if (foundImageIt != dummyImages.end()) {
- byteArray = foundImageIt.value();
- }
- currentImage.clear();
- auto fakePayloadReply = new FakePayloadReply(op, req, byteArray, nullptr);
- QMap<QNetworkRequest::KnownHeaders, QByteArray> additionalHeaders = {
- {QNetworkRequest::KnownHeaders::ContentTypeHeader, "image/jpeg"}};
- fakePayloadReply->_additionalHeaders = additionalHeaders;
- reply = fakePayloadReply;
- }
- } else {
- reply = new FakePayloadReply(op, req, {}, nullptr);
- }
-
- return reply;
- });
- };
- void testRequestThumbnails()
- {
- FolderMan *folderman = FolderMan::instance();
- QVERIFY(folderman);
- auto folder = FolderMan::instance()->folderForPath(fakeFolder.localPath());
- QVERIFY(folder);
- folder->setVirtualFilesEnabled(true);
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- ItemCompletedSpy completeSpy(fakeFolder);
- auto cleanup = [&]() {
- completeSpy.clear();
- };
- cleanup();
- // Create a virtual file for remote files
- for (const auto &dummyImageName : dummmyImageNames) {
- fakeFolder.remoteModifier().insert(dummyImageName, 256);
- }
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- cleanup();
- // just add records from fake folder's journal to real one's to make test work
- SyncJournalFileRecord record;
- auto realFolder = FolderMan::instance()->folderForPath(fakeFolder.localPath());
- QVERIFY(realFolder);
- for (const auto &dummyImageName : dummmyImageNames) {
- if (fakeFolder.syncJournal().getFileRecord(dummyImageName, &record)) {
- QVERIFY(realFolder->journalDb()->setFileRecord(record));
- }
- }
- // #1 Test every fake image fetching. Everything must succeed.
- for (const auto &dummyImageName : dummmyImageNames) {
- QEventLoop loop;
- QByteArray thumbnailReplyData;
- currentImage = dummyImageName;
- // emulate thumbnail request from a separate thread (just like the real shell extension does)
- std::thread t([&] {
- VfsShellExtensions::ThumbnailProviderIpc thumbnailProviderIpc;
- thumbnailReplyData = thumbnailProviderIpc.fetchThumbnailForFile(
- fakeFolder.localPath() + dummyImageName, QSize(256, 256));
- QMetaObject::invokeMethod(&loop, &QEventLoop::quit, Qt::QueuedConnection);
- });
- loop.exec();
- t.detach();
- QVERIFY(!thumbnailReplyData.isEmpty());
- const auto imageFromData = QImage::fromData(thumbnailReplyData);
- QVERIFY(!imageFromData.isNull());
- }
- // #2 Test wrong image fetching. It must fail.
- QEventLoop loop;
- QByteArray thumbnailReplyData;
- std::thread t1([&] {
- VfsShellExtensions::ThumbnailProviderIpc thumbnailProviderIpc;
- thumbnailReplyData = thumbnailProviderIpc.fetchThumbnailForFile(
- fakeFolder.localPath() + QString("A/photos/wrong.jpg"), QSize(256, 256));
- QMetaObject::invokeMethod(&loop, &QEventLoop::quit, Qt::QueuedConnection);
- });
- loop.exec();
- t1.detach();
- QVERIFY(thumbnailReplyData.isEmpty());
- // #3 Test one image fetching, but set incorrect size. It must fail.
- currentImage = dummyImages.keys().first();
- std::thread t2([&] {
- VfsShellExtensions::ThumbnailProviderIpc thumbnailProviderIpc;
- thumbnailReplyData = thumbnailProviderIpc.fetchThumbnailForFile(fakeFolder.localPath() + currentImage, {});
- QMetaObject::invokeMethod(&loop, &QEventLoop::quit, Qt::QueuedConnection);
- });
- loop.exec();
- t2.detach();
- QVERIFY(thumbnailReplyData.isEmpty());
- }
- void testRequestCustomStates()
- {
- FolderMan *folderman = FolderMan::instance();
- QVERIFY(folderman);
- auto folder = FolderMan::instance()->folderForPath(fakeFolder.localPath());
- QVERIFY(folder);
- folder->setVirtualFilesEnabled(true);
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- // just add records from fake folder's journal to real one's to make test work
- SyncJournalFileRecord record;
- auto realFolder = FolderMan::instance()->folderForPath(fakeFolder.localPath());
- QVERIFY(realFolder);
- for (auto it = std::begin(dummyFileStates); it != std::end(dummyFileStates); ++it) {
- if (fakeFolder.syncJournal().getFileRecord(it.key(), &record)) {
- record._isShared = it.value()._isShared;
- if (record._isShared) {
- record._remotePerm.setPermission(OCC::RemotePermissions::Permissions::IsShared);
- }
- record._lockstate._locked = it.value()._isLocked;
- if (record._lockstate._locked) {
- record._lockstate._lockOwnerId = "admin@example.cloud.com";
- record._lockstate._lockOwnerDisplayName = "Admin";
- record._lockstate._lockOwnerType = static_cast<int>(SyncFileItem::LockOwnerType::UserLock);
- record._lockstate._lockTime = QDateTime::currentMSecsSinceEpoch();
- record._lockstate._lockTimeout = 1000 * 60 * 60;
- }
- QVERIFY(fakeFolder.syncJournal().setFileRecord(record));
- QVERIFY(realFolder->journalDb()->setFileRecord(record));
- }
- }
- // #1 Test every file's states fetching. Everything must succeed.
- for (auto it = std::cbegin(dummyFileStates); it != std::cend(dummyFileStates); ++it) {
- QEventLoop loop;
- QVariantList customStates;
- std::thread t([&] {
- VfsShellExtensions::CustomStateProviderIpc customStateProviderIpc;
- customStates = customStateProviderIpc.fetchCustomStatesForFile(fakeFolder.localPath() + it.key());
- QMetaObject::invokeMethod(&loop, &QEventLoop::quit, Qt::QueuedConnection);
- });
- loop.exec();
- t.detach();
- QVERIFY(!customStates.isEmpty() || (!it.value()._isLocked && !it.value()._isShared));
- }
- // #2 Test wrong file's states fetching. It must fail.
- QEventLoop loop;
- QVariantList customStates;
- std::thread t1([&] {
- VfsShellExtensions::CustomStateProviderIpc customStateProviderIpc;
- customStates = customStateProviderIpc.fetchCustomStatesForFile(fakeFolder.localPath() + QStringLiteral("A/files/wrong.jpg"));
- QMetaObject::invokeMethod(&loop, &QEventLoop::quit, Qt::QueuedConnection);
- });
- loop.exec();
- t1.detach();
- QVERIFY(customStates.isEmpty());
- // #3 Test wrong file states fetching. It must fail.
- customStates.clear();
- std::thread t2([&] {
- VfsShellExtensions::CustomStateProviderIpc customStateProviderIpc;
- customStates = customStateProviderIpc.fetchCustomStatesForFile(fakeFolder.localPath() + QStringLiteral("A/files/test_non_shared_and_non_locked_file.txt"));
- QMetaObject::invokeMethod(&loop, &QEventLoop::quit, Qt::QueuedConnection);
- });
- loop.exec();
- t2.detach();
- QVERIFY(customStates.isEmpty());
- // reset all share states to make sure we'll get new states when fetching
- for (auto it = std::begin(dummyFileStates); it != std::end(dummyFileStates); ++it) {
- if (fakeFolder.syncJournal().getFileRecord(it.key(), &record)) {
- record._remotePerm.unsetPermission(OCC::RemotePermissions::Permissions::IsShared);
- record._isShared = false;
- QVERIFY(fakeFolder.syncJournal().setFileRecord(record));
- QVERIFY(realFolder->journalDb()->setFileRecord(record));
- }
- }
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- //
- // wait enough time to make shares' state invalid
- QTest::qWait(shellExtensionServerOverrideIntervalMs + 1000);
- // #4 Test every file's states fetching. Everything must succeed.
- for (auto it = std::cbegin(dummyFileStates); it != std::cend(dummyFileStates); ++it) {
- QEventLoop loop;
- QVariantList customStates;
- std::thread t([&] {
- VfsShellExtensions::CustomStateProviderIpc customStateProviderIpc;
- customStates = customStateProviderIpc.fetchCustomStatesForFile(fakeFolder.localPath() + it.key());
- QMetaObject::invokeMethod(&loop, &QEventLoop::quit, Qt::QueuedConnection);
- });
- loop.exec();
- t.detach();
- QVERIFY(!customStates.isEmpty() || (!it.value()._isLocked && !it.value()._isShared));
- if (!customStates.isEmpty()) {
- const auto lockedIndex = QString(CUSTOM_STATE_ICON_LOCKED_INDEX).toInt() - QString(CUSTOM_STATE_ICON_INDEX_OFFSET).toInt();
- const auto sharedIndex = QString(CUSTOM_STATE_ICON_SHARED_INDEX).toInt() - QString(CUSTOM_STATE_ICON_INDEX_OFFSET).toInt();
- if (customStates.contains(lockedIndex) && customStates.contains(sharedIndex)) {
- QVERIFY(it.value()._isLocked && it.value()._isShared);
- }
- if (customStates.contains(lockedIndex)) {
- QVERIFY(it.value()._isLocked);
- }
- if (customStates.contains(sharedIndex)) {
- QVERIFY(it.value()._isShared);
- }
- }
- }
- // #5 Test no shares response for a file
- QTest::qWait(shellExtensionServerOverrideIntervalMs + 1000);
- TestCfApiShellExtensionsIPC::replyWithNoShares = true;
- customStates.clear();
- std::thread t3([&] {
- VfsShellExtensions::CustomStateProviderIpc customStateProviderIpc;
- customStates = customStateProviderIpc.fetchCustomStatesForFile(fakeFolder.localPath() + QStringLiteral("A/files/test_non_shared_and_non_locked_file.txt"));
- QMetaObject::invokeMethod(&loop, &QEventLoop::quit, Qt::QueuedConnection);
- });
- loop.exec();
- t3.detach();
- QVERIFY(customStates.isEmpty());
- }
- void cleanupTestCase()
- {
- VfsShellExtensions::ThumbnailProviderIpc::overrideServerName.clear();
- if (auto folder = FolderMan::instance()->folderForPath(fakeFolder.localPath())) {
- folder->setVirtualFilesEnabled(false);
- }
- FolderMan::instance()->unloadAndDeleteAllFolders();
- if (auto accountToDelete = OCC::AccountManager::instance()->accounts().first()) {
- OCC::AccountManager::instance()->deleteAccount(accountToDelete.data());
- }
- }
- };
- bool TestCfApiShellExtensionsIPC::replyWithNoShares = false;
- QTEST_GUILESS_MAIN(TestCfApiShellExtensionsIPC)
- #include "testcfapishellextensionsipc.moc"
|