| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444 |
- /*
- * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@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 "sharetestutils.h"
- #include "testhelper.h"
- using namespace OCC;
- FakeShareDefinition::FakeShareDefinition(ShareTestHelper *helper,
- const Share::ShareType type,
- const QString &shareWith,
- const QString &displayString,
- const QString &password,
- const QString ¬e,
- const QString &expiration)
- {
- ++helper->latestShareId;
- const auto idString = QString::number(helper->latestShareId);
- fileDefinition = helper->fakeFileDefinition;
- shareId = idString;
- shareCanDelete = true;
- shareCanEdit = true;
- shareUidOwner = helper->account->davUser();;
- shareDisplayNameOwner = helper->account->davDisplayName();
- sharePassword = password;
- sharePermissions = static_cast<int>(SharePermissions(SharePermissionRead |
- SharePermissionUpdate |
- SharePermissionCreate |
- SharePermissionDelete |
- SharePermissionShare));
- shareNote = note;
- shareHideDownload = 0;
- shareExpiration = expiration;
- shareSendPasswordByTalk = false;
- shareType = type;
- const auto token = QString(QStringLiteral("GQ4aLrZEdJJkopW-") + idString);
- // Weird, but it's what the server does
- const auto finalShareWith = type == Share::TypeLink ? password : shareWith;
- const auto shareWithDisplayName = type == Share::TypeLink ? QStringLiteral("(Shared Link)") : displayString;
- const auto linkLabel = type == Share::TypeLink ? displayString : QString();
- const auto linkName = linkShareLabel;
- const auto linkUrl = type == Share::TypeLink ? QString(helper->account->davUrl().toString() + QStringLiteral("/s/") + token) : QString();
- shareShareWith = finalShareWith;
- shareShareWithDisplayName = shareWithDisplayName;
- shareToken = token;
- linkShareName = linkName;
- linkShareLabel = linkLabel;
- linkShareUrl = linkUrl;
- }
- QJsonObject FakeShareDefinition::toShareJsonObject() const
- {
- QJsonObject newShareJson;
- newShareJson.insert("uid_file_owner", fileDefinition.fileOwnerUid);
- newShareJson.insert("displayname_file_owner", fileDefinition.fileOwnerDisplayName);
- newShareJson.insert("file_target", fileDefinition.fileTarget);
- newShareJson.insert("has_preview", fileDefinition.fileHasPreview);
- newShareJson.insert("file_parent", fileDefinition.fileFileParent);
- newShareJson.insert("file_source", fileDefinition.fileSource);
- newShareJson.insert("item_source", fileDefinition.fileItemSource);
- newShareJson.insert("item_type", fileDefinition.fileItemType);
- newShareJson.insert("mail_send", fileDefinition.fileMailSend);
- newShareJson.insert("mimetype", fileDefinition.fileMimeType);
- newShareJson.insert("parent", fileDefinition.fileParent);
- newShareJson.insert("path", fileDefinition.filePath);
- newShareJson.insert("storage", fileDefinition.fileStorage);
- newShareJson.insert("storage_id", fileDefinition.fileStorageId);
- newShareJson.insert("id", shareId);
- newShareJson.insert("can_delete", shareCanDelete);
- newShareJson.insert("can_edit", shareCanEdit);
- newShareJson.insert("uid_owner", shareUidOwner);
- newShareJson.insert("displayname_owner", shareDisplayNameOwner);
- newShareJson.insert("password", sharePassword);
- newShareJson.insert("permissions", sharePermissions);
- newShareJson.insert("note", shareNote);
- newShareJson.insert("hide_download", shareHideDownload);
- newShareJson.insert("expiration", shareExpiration);
- newShareJson.insert("send_password_by_talk", shareSendPasswordByTalk);
- newShareJson.insert("share_type", shareType);
- newShareJson.insert("share_with", shareShareWith);
- newShareJson.insert("share_with_displayname", shareShareWithDisplayName);
- newShareJson.insert("token", shareToken);
- newShareJson.insert("name", linkShareName);
- newShareJson.insert("label", linkShareLabel);
- newShareJson.insert("url", linkShareUrl);
- return newShareJson;
- }
- QByteArray FakeShareDefinition::toRequestReply() const
- {
- const auto shareJson = toShareJsonObject();
- return jsonValueToOccReply(shareJson);
- }
- // Below is ShareTestHelper
- ShareTestHelper::ShareTestHelper(QObject *parent)
- : QObject(parent)
- {
- }
- ShareTestHelper::~ShareTestHelper()
- {
- const auto folder = FolderMan::instance()->folder(fakeFolder.localPath());
- if (folder) {
- FolderMan::instance()->removeFolder(folder);
- }
- AccountManager::instance()->deleteAccount(accountState.data());
- }
- void ShareTestHelper::setup()
- {
- _fakeQnam.reset(new FakeQNAM({}));
- _fakeQnam->setOverride([this](const QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *device) {
- return qnamOverride(op, req, device);
- });
- account = Account::create();
- account->setCredentials(new FakeCredentials{_fakeQnam.data()});
- account->setUrl(QUrl(("owncloud://somehost/owncloud")));
- account->setCapabilities(_fakeCapabilities);
- accountState = new AccountState(account);
- AccountManager::instance()->addAccount(account);
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- fakeFolder.localModifier().insert(testFileName);
- const auto folderMan = FolderMan::instance();
- QCOMPARE(folderMan, &fm);
- auto folderDef = folderDefinition(fakeFolder.localPath());
- folderDef.targetPath = QString();
- QVERIFY(folderMan->addFolder(accountState.data(), folderDef));
- const auto folder = FolderMan::instance()->folder(fakeFolder.localPath());
- QVERIFY(folder);
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- const auto fakeFileInfo = fakeFolder.remoteModifier().find(testFileName);
- QVERIFY(fakeFileInfo);
- fakeFileInfo->permissions.setPermission(RemotePermissions::CanReshare);
- QVERIFY(fakeFolder.syncOnce());
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- QVERIFY(fakeFileInfo->permissions.CanReshare);
- _fakeCapabilities = QVariantMap {
- {QStringLiteral("files_sharing"), QVariantMap {
- {QStringLiteral("api_enabled"), true},
- {QStringLiteral("default_permissions"), 19},
- {QStringLiteral("public"), QVariantMap {
- {QStringLiteral("enabled"), true},
- {QStringLiteral("expire_date"), QVariantMap {
- {QStringLiteral("days"), 30},
- {QStringLiteral("enforced"), false},
- }},
- {QStringLiteral("expire_date_internal"), QVariantMap {
- {QStringLiteral("days"), 30},
- {QStringLiteral("enforced"), false},
- }},
- {QStringLiteral("expire_date_remote"), QVariantMap {
- {QStringLiteral("days"), 30},
- {QStringLiteral("enforced"), false},
- }},
- {QStringLiteral("password"), QVariantMap {
- {QStringLiteral("enforced"), false},
- }},
- }},
- {QStringLiteral("sharebymail"), QVariantMap {
- {QStringLiteral("enabled"), true},
- {QStringLiteral("password"), QVariantMap {
- {QStringLiteral("enforced"), false},
- }},
- }},
- }},
- };
- // Generate test data
- // Properties that apply to the file generally
- const auto fileOwnerUid = account->davUser();
- const auto fileOwnerDisplayName = account->davDisplayName();
- const auto fileTarget = QString(QStringLiteral("/") + fakeFileInfo->name);
- const auto fileHasPreview = true;
- const auto fileFileParent = QString(fakeFolder.remoteModifier().fileId);
- const auto fileSource = QString(fakeFileInfo->fileId);
- const auto fileItemSource = fileSource;
- const auto fileItemType = QStringLiteral("file");
- const auto fileMailSend = 0;
- const auto fileMimeType = QStringLiteral("text/markdown");
- const auto fileParent = QString();
- const auto filePath = fakeFileInfo->path();
- const auto fileStorage = 3;
- const auto fileStorageId = QString(QStringLiteral("home::") + account->davUser());
- fakeFileDefinition = FakeFileReplyDefinition {
- fileOwnerUid,
- fileOwnerDisplayName,
- fileTarget,
- fileHasPreview,
- fileFileParent,
- fileSource,
- fileItemSource,
- fileItemType,
- fileMailSend,
- fileMimeType,
- fileParent,
- filePath,
- fileStorage,
- fileStorageId,
- };
- emit setupSucceeded();
- }
- QNetworkReply *ShareTestHelper::qnamOverride(QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *device)
- {
- QNetworkReply *reply = nullptr;
- const auto reqUrl = req.url();
- const auto reqRawPath = reqUrl.path();
- const auto reqPath = reqRawPath.startsWith("/owncloud/") ? reqRawPath.mid(10) : reqRawPath;
- qDebug() << req.url() << reqPath << op;
- // Properly formatted PROPFIND URL goes something like:
- // https://cloud.nextcloud.com/remote.php/dav/files/claudio/Readme.md
- if(reqPath.endsWith(testFileName) && req.attribute(QNetworkRequest::CustomVerbAttribute) == "PROPFIND") {
- reply = new FakePropfindReply(fakeFolder.remoteModifier(), op, req, this);
- } else if (req.url().toString().startsWith(accountState->account()->url().toString()) &&
- reqPath.startsWith(QStringLiteral("ocs/v2.php/apps/files_sharing/api/v1/shares"))) {
- if (op == QNetworkAccessManager::PostOperation) {
- reply = handleSharePostOperation(op, req, device);
- } else if(req.attribute(QNetworkRequest::CustomVerbAttribute) == "DELETE") {
- reply = handleShareDeleteOperation(op, req, reqPath);
- } else if(op == QNetworkAccessManager::PutOperation) {
- reply = handleSharePutOperation(op, req, reqPath, device);
- } else if(req.attribute(QNetworkRequest::CustomVerbAttribute) == "GET") {
- reply = handleShareGetOperation(op, req, reqPath);
- }
- } else {
- reply = new FakeErrorReply(op, req, this, 404, _fake404Response);
- }
- return reply;
- }
- QNetworkReply *ShareTestHelper::handleSharePostOperation(QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *device)
- {
- QNetworkReply *reply = nullptr;
- // POST https://somehost/owncloud/ocs/v2.php/apps/files_sharing/api/v1/shares?format=json
- // Header: { Ocs-APIREQUEST: true, Content-Type: application/x-www-form-urlencoded, X-Request-ID: 1527752d-e147-4da7-89b8-fb06315a5fad, }
- // Data: [path=file.md&shareType=3]"
- const QUrlQuery urlQuery(req.url());
- const auto formatParam = urlQuery.queryItemValue(QStringLiteral("format"));
- if (formatParam == QStringLiteral("json")) {
- device->open(QIODevice::ReadOnly);
- const auto requestBody = device->readAll();
- device->close();
- const auto requestData = requestBody.split('&');
- // We don't care about path since we know the file we are testing with
- auto requestShareType = -10; // Just in case
- QString requestShareWith;
- QString requestName;
- QString requestPassword;
- for(const auto &data : requestData) {
- const auto requestDataUrl = QUrl::fromPercentEncoding(data);
- const QString requestDataUrlString(requestDataUrl);
- if (data.contains("shareType=")) {
- const auto shareTypeString = requestDataUrlString.mid(10);
- requestShareType = Share::ShareType(shareTypeString.toInt());
- } else if (data.contains("shareWith=")) {
- requestShareWith = data.mid(10);
- } else if (data.contains("name=")) {
- requestName = data.mid(5);
- } else if (data.contains("password=")) {
- requestPassword = data.mid(9);
- }
- }
- if (requestPassword.isEmpty() &&
- ((requestShareType == Share::TypeEmail && account->capabilities().shareEmailPasswordEnforced()) ||
- (requestShareType == Share::TypeLink && account->capabilities().sharePublicLinkEnforcePassword()))) {
- reply = new FakePayloadReply(op, req, _fake403Response, searchResultsReplyDelay, _fakeQnam.data());
- } else if (requestShareType >= 0) {
- const auto shareType = Share::ShareType(requestShareType);
- reply = new FakePayloadReply(op, req, createNewShare(shareType, requestShareWith, requestPassword), searchResultsReplyDelay, _fakeQnam.data());
- }
- }
- return reply;
- }
- QNetworkReply *ShareTestHelper::handleSharePutOperation(const QNetworkAccessManager::Operation op, const QNetworkRequest &req, const QString &reqPath, QIODevice *device)
- {
- QNetworkReply *reply = nullptr;
- const auto splitUrlPath = reqPath.split('/');
- const auto shareId = splitUrlPath.last();
- const QUrlQuery urlQuery(req.url());
- const auto formatParam = urlQuery.queryItemValue(QStringLiteral("format"));
- if (formatParam == QStringLiteral("json")) {
- device->open(QIODevice::ReadOnly);
- const auto requestBody = device->readAll();
- device->close();
- const auto requestData = requestBody.split('&');
- const auto existingShareIterator = std::find_if(_sharesReplyData.cbegin(), _sharesReplyData.cend(), [&shareId](const QJsonValue &value) {
- return value.toObject().value("id").toString() == shareId;
- });
- if (existingShareIterator == _sharesReplyData.cend()) {
- reply = new FakeErrorReply(op, req, this, 404, _fake404Response);
- } else {
- const auto existingShareValue = *existingShareIterator;
- auto shareObject = existingShareValue.toObject();
- for (const auto &requestDataItem : requestData) {
- const auto requestSplit = requestDataItem.split('=');
- auto requestKey = requestSplit.first();
- auto requestValue = requestSplit.last();
- // We send expireDate without time but the server returns with time at 00:00:00
- if (requestKey == "expireDate") {
- requestKey = "expiration";
- requestValue.append(" 00:00:00");
- }
- shareObject.insert(QString(requestKey), QString(requestValue));
- }
- _sharesReplyData.replace(existingShareIterator - _sharesReplyData.cbegin(), shareObject);
- reply = new FakePayloadReply(op, req, jsonValueToOccReply(shareObject), searchResultsReplyDelay, _fakeQnam.data());
- }
- }
- return reply;
- }
- QNetworkReply *ShareTestHelper::handleShareDeleteOperation(const QNetworkAccessManager::Operation op, const QNetworkRequest &req, const QString &reqPath)
- {
- QNetworkReply *reply = nullptr;
- const auto splitUrlPath = reqPath.split('/');
- const auto shareId = splitUrlPath.last();
- const auto existingShareIterator = std::find_if(_sharesReplyData.cbegin(), _sharesReplyData.cend(), [&shareId](const QJsonValue &value) {
- return value.toObject().value("id").toString() == shareId;
- });
- if (existingShareIterator == _sharesReplyData.cend()) {
- reply = new FakeErrorReply(op, req, this, 404, _fake404Response);
- } else {
- _sharesReplyData.removeAt(existingShareIterator - _sharesReplyData.cbegin());
- reply = new FakePayloadReply(op, req, _fake200JsonResponse, searchResultsReplyDelay, _fakeQnam.data());
- }
- return reply;
- }
- QNetworkReply *ShareTestHelper::handleShareGetOperation(const QNetworkAccessManager::Operation op, const QNetworkRequest &req, const QString &reqPath)
- {
- QNetworkReply *reply = nullptr;
- // Properly formatted request to fetch shares goes something like:
- // GET https://somehost/owncloud/ocs/v2.php/apps/files_sharing/api/v1/shares?path=file.md&reshares=true&format=json
- // Header: { Ocs-APIREQUEST: true, Content-Type: application/x-www-form-urlencoded, X-Request-ID: 8ba8960d-ca0d-45ba-abf4-03ab95ba6064, }
- // Data: []
- const auto urlQuery = QUrlQuery(req.url());
- const auto pathParam = urlQuery.queryItemValue(QStringLiteral("path"));
- const auto resharesParam = urlQuery.queryItemValue(QStringLiteral("reshares"));
- const auto formatParam = urlQuery.queryItemValue(QStringLiteral("format"));
- if (formatParam != QStringLiteral("json") || (!pathParam.isEmpty() && !pathParam.endsWith(QString(testFileName)))) {
- reply = new FakeErrorReply(op, req, this, 400, _fake400Response);
- } else if (reqPath.contains(QStringLiteral("ocs/v2.php/apps/files_sharing/api/v1/shares"))) {
- reply = new FakePayloadReply(op, req, jsonValueToOccReply(_sharesReplyData), searchResultsReplyDelay, _fakeQnam.data());
- }
- return reply;
- }
- const QByteArray ShareTestHelper::createNewShare(const Share::ShareType shareType, const QString &shareWith, const QString &password)
- {
- const auto displayString = shareType == Share::TypeLink ? QString() : shareWith;
- const FakeShareDefinition newShareDefinition(this,
- shareType,
- shareWith,
- displayString,
- password);
- _sharesReplyData.append(newShareDefinition.toShareJsonObject());
- return newShareDefinition.toRequestReply();
- }
- int ShareTestHelper::shareCount() const
- {
- return _sharesReplyData.count();
- }
- void ShareTestHelper::appendShareReplyData(const FakeShareDefinition &definition)
- {
- _sharesReplyData.append(definition.toShareJsonObject());
- }
- void ShareTestHelper::resetTestShares()
- {
- _sharesReplyData = QJsonArray();
- }
- void ShareTestHelper::resetTestData()
- {
- resetTestShares();
- account->setCapabilities(_fakeCapabilities);
- }
|