From 05927b68a83fca5567e4bebad4f41a3345b3e478 Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Fri, 15 Sep 2017 14:24:34 +0200 Subject: [PATCH] Private links: Retrieve link through propfind property #6020 * The sharing ui does a propfind anyway: use that to query the new property as well! * For the socket api, asynchronously query the server for the right url when an action that needs it is triggered. The old, manually generated URL will be used as fallback in case the server doesn't support the new property or the property can't be retrieved for some reason. Depends on owncloud/core#29021 --- src/common/syncjournalfilerecord.h | 2 +- src/gui/sharedialog.cpp | 22 +++++--- src/gui/sharedialog.h | 5 +- src/gui/shareusergroupwidget.cpp | 10 ++-- src/gui/shareusergroupwidget.h | 4 +- src/gui/socketapi.cpp | 87 ++++++++++++++++++++---------- src/gui/socketapi.h | 4 +- src/libsync/account.cpp | 2 +- src/libsync/account.h | 8 ++- src/libsync/capabilities.cpp | 5 ++ src/libsync/capabilities.h | 3 ++ 11 files changed, 102 insertions(+), 50 deletions(-) diff --git a/src/common/syncjournalfilerecord.h b/src/common/syncjournalfilerecord.h index ca8430469..ba1c7ced0 100644 --- a/src/common/syncjournalfilerecord.h +++ b/src/common/syncjournalfilerecord.h @@ -37,7 +37,7 @@ class OCSYNC_EXPORT SyncJournalFileRecord public: SyncJournalFileRecord(); - bool isValid() + bool isValid() const { return !_path.isEmpty(); } diff --git a/src/gui/sharedialog.cpp b/src/gui/sharedialog.cpp index 24a912fdc..aa47598e9 100644 --- a/src/gui/sharedialog.cpp +++ b/src/gui/sharedialog.cpp @@ -46,7 +46,7 @@ ShareDialog::ShareDialog(QPointer accountState, , _sharePath(sharePath) , _localPath(localPath) , _maxSharingPermissions(maxSharingPermissions) - , _numericFileId(numericFileId) + , _privateLinkUrl(accountState->account()->deprecatedPrivateLinkUrl(numericFileId).toString(QUrl::FullyEncoded)) , _linkWidget(NULL) , _userGroupWidget(NULL) , _progressIndicator(NULL) @@ -130,10 +130,13 @@ ShareDialog::ShareDialog(QPointer accountState, // Server versions >= 9.1 support the "share-permissions" property // older versions will just return share-permissions: "" auto job = new PropfindJob(accountState->account(), _sharePath); - job->setProperties(QList() << "http://open-collaboration-services.org/ns:share-permissions"); + job->setProperties( + QList() + << "http://open-collaboration-services.org/ns:share-permissions" + << "http://owncloud.org/ns:privatelink"); job->setTimeout(10 * 1000); - connect(job, SIGNAL(result(QVariantMap)), SLOT(slotMaxSharingPermissionsReceived(QVariantMap))); - connect(job, SIGNAL(finishedWithError(QNetworkReply *)), SLOT(slotMaxSharingPermissionsError())); + connect(job, SIGNAL(result(QVariantMap)), SLOT(slotPropfindReceived(QVariantMap))); + connect(job, SIGNAL(finishedWithError(QNetworkReply *)), SLOT(slotPropfindError())); job->start(); } @@ -149,18 +152,23 @@ void ShareDialog::done(int r) QDialog::done(r); } -void ShareDialog::slotMaxSharingPermissionsReceived(const QVariantMap &result) +void ShareDialog::slotPropfindReceived(const QVariantMap &result) { const QVariant receivedPermissions = result["share-permissions"]; if (!receivedPermissions.toString().isEmpty()) { _maxSharingPermissions = static_cast(receivedPermissions.toInt()); qCInfo(lcSharing) << "Received sharing permissions for" << _sharePath << _maxSharingPermissions; } + auto privateLinkUrl = result["privatelink"].toString(); + if (!privateLinkUrl.isEmpty()) { + qCInfo(lcSharing) << "Received private link url for" << _sharePath << privateLinkUrl; + _privateLinkUrl = privateLinkUrl; + } showSharingUi(); } -void ShareDialog::slotMaxSharingPermissionsError() +void ShareDialog::slotPropfindError() { // On error show the share ui anyway. The user can still see shares, // delete them and so on, even though adding new shares or granting @@ -194,7 +202,7 @@ void ShareDialog::showSharingUi() && _accountState->account()->serverVersionInt() >= Account::makeServerVersion(8, 2, 0); if (userGroupSharing) { - _userGroupWidget = new ShareUserGroupWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, _numericFileId, this); + _userGroupWidget = new ShareUserGroupWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, _privateLinkUrl, this); _ui->shareWidgets->addTab(_userGroupWidget, tr("Users and Groups")); _userGroupWidget->getShares(); } diff --git a/src/gui/sharedialog.h b/src/gui/sharedialog.h index 50aeea2e1..8af3f3fff 100644 --- a/src/gui/sharedialog.h +++ b/src/gui/sharedialog.h @@ -49,8 +49,8 @@ public: private slots: void done(int r); - void slotMaxSharingPermissionsReceived(const QVariantMap &result); - void slotMaxSharingPermissionsError(); + void slotPropfindReceived(const QVariantMap &result); + void slotPropfindError(); void slotThumbnailFetched(const int &statusCode, const QByteArray &reply); void slotAccountStateChanged(int state); @@ -63,6 +63,7 @@ private: QString _localPath; SharePermissions _maxSharingPermissions; QByteArray _numericFileId; + QString _privateLinkUrl; ShareLinkWidget *_linkWidget; ShareUserGroupWidget *_userGroupWidget; diff --git a/src/gui/shareusergroupwidget.cpp b/src/gui/shareusergroupwidget.cpp index 8cecac2a4..5b505f21a 100644 --- a/src/gui/shareusergroupwidget.cpp +++ b/src/gui/shareusergroupwidget.cpp @@ -48,7 +48,7 @@ ShareUserGroupWidget::ShareUserGroupWidget(AccountPtr account, const QString &sharePath, const QString &localPath, SharePermissions maxSharingPermissions, - const QByteArray &numericFileId, + const QString &privateLinkUrl, QWidget *parent) : QWidget(parent) , _ui(new Ui::ShareUserGroupWidget) @@ -56,7 +56,7 @@ ShareUserGroupWidget::ShareUserGroupWidget(AccountPtr account, , _sharePath(sharePath) , _localPath(localPath) , _maxSharingPermissions(maxSharingPermissions) - , _numericFileId(numericFileId) + , _privateLinkUrl(privateLinkUrl) , _disableCompleterActivated(false) { setAttribute(Qt::WA_DeleteOnClose); @@ -323,19 +323,19 @@ void ShareUserGroupWidget::displayError(int code, const QString &message) void ShareUserGroupWidget::slotPrivateLinkOpenBrowser() { - Utility::openBrowser(_account->filePermalinkUrl(_numericFileId), this); + Utility::openBrowser(_privateLinkUrl, this); } void ShareUserGroupWidget::slotPrivateLinkCopy() { - QApplication::clipboard()->setText(_account->filePermalinkUrl(_numericFileId).toString()); + QApplication::clipboard()->setText(_privateLinkUrl); } void ShareUserGroupWidget::slotPrivateLinkEmail() { Utility::openEmailComposer( tr("I shared something with you"), - _account->filePermalinkUrl(_numericFileId).toString(), + _privateLinkUrl, this); } diff --git a/src/gui/shareusergroupwidget.h b/src/gui/shareusergroupwidget.h index fbc2bdfa4..1feca5f26 100644 --- a/src/gui/shareusergroupwidget.h +++ b/src/gui/shareusergroupwidget.h @@ -57,7 +57,7 @@ public: const QString &sharePath, const QString &localPath, SharePermissions maxSharingPermissions, - const QByteArray &numericFileId, + const QString &privateLinkUrl, QWidget *parent = 0); ~ShareUserGroupWidget(); @@ -89,7 +89,7 @@ private: QString _sharePath; QString _localPath; SharePermissions _maxSharingPermissions; - QByteArray _numericFileId; + QString _privateLinkUrl; QCompleter *_completer; ShareeModel *_completerModel; diff --git a/src/gui/socketapi.cpp b/src/gui/socketapi.cpp index 9261cef83..0073e6154 100644 --- a/src/gui/socketapi.cpp +++ b/src/gui/socketapi.cpp @@ -491,23 +491,70 @@ void SocketApi::command_SHARE_MENU_TITLE(const QString &, SocketListener *listen listener->sendMessage(QLatin1String("SHARE_MENU_TITLE:") + tr("Share with %1", "parameter is ownCloud").arg(Theme::instance()->appNameGUI())); } -void SocketApi::command_COPY_PRIVATE_LINK(const QString &localFile, SocketListener *) +// Fetches the private link url asynchronously and then calls the target slot +void fetchPrivateLinkUrl(const QString &localFile, SocketApi *target, void (SocketApi::*targetFun)(const QString &url) const) { - auto url = getPrivateLinkUrl(localFile); - if (!url.isEmpty()) { - QApplication::clipboard()->setText(url.toString()); + Folder *shareFolder = FolderMan::instance()->folderForPath(localFile); + if (!shareFolder) { + qCWarning(lcSocketApi) << "Unknown path" << localFile; + return; + } + + const QString localFileClean = QDir::cleanPath(localFile); + const QString file = localFileClean.mid(shareFolder->cleanPath().length() + 1); + + // Generate private link ourselves: used as a fallback + const SyncJournalFileRecord rec = shareFolder->journalDb()->getFileRecord(file); + if (!rec.isValid()) + return; + const QString oldUrl = + shareFolder->accountState()->account()->deprecatedPrivateLinkUrl(rec.numericFileId()).toString(QUrl::FullyEncoded); + + // If the server doesn't have the property, use the old url directly. + if (!shareFolder->accountState()->account()->capabilities().privateLinkPropertyAvailable()) { + (target->*targetFun)(oldUrl); + return; } + + // Retrieve the new link by PROPFIND + PropfindJob *job = new PropfindJob(shareFolder->accountState()->account(), file, target); + job->setProperties(QList() << "http://owncloud.org/ns:privatelink"); + job->setTimeout(10 * 1000); + QObject::connect(job, &PropfindJob::result, target, [=](const QVariantMap &result) { + auto privateLinkUrl = result["privatelink"].toString(); + if (!privateLinkUrl.isEmpty()) { + (target->*targetFun)(privateLinkUrl); + } else { + (target->*targetFun)(oldUrl); + } + }); + QObject::connect(job, &PropfindJob::finishedWithError, target, [=](QNetworkReply *) { + (target->*targetFun)(oldUrl); + }); + job->start(); +} + +void SocketApi::command_COPY_PRIVATE_LINK(const QString &localFile, SocketListener *) +{ + fetchPrivateLinkUrl(localFile, this, &SocketApi::copyPrivateLinkToClipboard); } void SocketApi::command_EMAIL_PRIVATE_LINK(const QString &localFile, SocketListener *) { - auto url = getPrivateLinkUrl(localFile); - if (!url.isEmpty()) { - Utility::openEmailComposer( - tr("I shared something with you"), - url.toString(QUrl::FullyEncoded), - 0); - } + fetchPrivateLinkUrl(localFile, this, &SocketApi::emailPrivateLink); +} + +void SocketApi::copyPrivateLinkToClipboard(const QString &link) const +{ + QApplication::clipboard()->setText(link); +} + +void SocketApi::emailPrivateLink(const QString &link) const +{ + Utility::openEmailComposer( + tr("I shared something with you"), + link, + 0); } void SocketApi::command_GET_STRINGS(const QString &, SocketListener *listener) @@ -533,22 +580,4 @@ QString SocketApi::buildRegisterPathMessage(const QString &path) return message; } -QUrl SocketApi::getPrivateLinkUrl(const QString &localFile) const -{ - Folder *shareFolder = FolderMan::instance()->folderForPath(localFile); - if (!shareFolder) { - qCWarning(lcSocketApi) << "Unknown path" << localFile; - return QUrl(); - } - - const QString localFileClean = QDir::cleanPath(localFile); - const QString file = localFileClean.mid(shareFolder->cleanPath().length() + 1); - - SyncJournalFileRecord rec = shareFolder->journalDb()->getFileRecord(file); - if (rec.isValid()) { - return shareFolder->accountState()->account()->filePermalinkUrl(rec.numericFileId()); - } - return QUrl(); -} - } // namespace OCC diff --git a/src/gui/socketapi.h b/src/gui/socketapi.h index 2b7b4fe9f..25eb3c72d 100644 --- a/src/gui/socketapi.h +++ b/src/gui/socketapi.h @@ -64,6 +64,9 @@ private slots: void slotReadSocket(); void broadcastStatusPushMessage(const QString &systemPath, SyncFileStatus fileStatus); + void copyPrivateLinkToClipboard(const QString &link) const; + void emailPrivateLink(const QString &link) const; + private: void broadcastMessage(const QString &msg, bool doWait = false); @@ -84,7 +87,6 @@ private: Q_INVOKABLE void command_GET_STRINGS(const QString &argument, SocketListener *listener); QString buildRegisterPathMessage(const QString &path); - QUrl getPrivateLinkUrl(const QString &localFile) const; QSet _registeredAliases; QList _listeners; diff --git a/src/libsync/account.cpp b/src/libsync/account.cpp index 7f2fadb29..61a99c065 100644 --- a/src/libsync/account.cpp +++ b/src/libsync/account.cpp @@ -162,7 +162,7 @@ QUrl Account::davUrl() const return Utility::concatUrlPath(url(), davPath()); } -QUrl Account::filePermalinkUrl(const QByteArray &numericFileId) const +QUrl Account::deprecatedPrivateLinkUrl(const QByteArray &numericFileId) const { return Utility::concatUrlPath(url(), QLatin1String("/index.php/f/") + QUrl::toPercentEncoding(QString::fromLatin1(numericFileId))); diff --git a/src/libsync/account.h b/src/libsync/account.h index c9f361feb..4f300ce97 100644 --- a/src/libsync/account.h +++ b/src/libsync/account.h @@ -108,8 +108,12 @@ public: /** Returns webdav entry URL, based on url() */ QUrl davUrl() const; - /** Returns a permalink url for a file */ - QUrl filePermalinkUrl(const QByteArray &numericFileId) const; + /** Returns the legacy permalink url for a file. + * + * This uses the old way of manually building the url. New code should + * use the "privatelink" property accessible via PROPFIND. + */ + QUrl deprecatedPrivateLinkUrl(const QByteArray &numericFileId) const; /** Holds the accounts credentials */ AbstractCredentials *credentials() const; diff --git a/src/libsync/capabilities.cpp b/src/libsync/capabilities.cpp index 4722c0b99..4dfc33b89 100644 --- a/src/libsync/capabilities.cpp +++ b/src/libsync/capabilities.cpp @@ -132,6 +132,11 @@ bool Capabilities::chunkingParallelUploadDisabled() const return _capabilities["dav"].toMap()["chunkingParallelUploadDisabled"].toBool(); } +bool Capabilities::privateLinkPropertyAvailable() const +{ + return _capabilities["files"].toMap()["privateLinks"].toBool(); +} + QList Capabilities::httpErrorCodesThatResetFailingChunkedUploads() const { QList list; diff --git a/src/libsync/capabilities.h b/src/libsync/capabilities.h index 6baaaa0ec..9b15edb9c 100644 --- a/src/libsync/capabilities.h +++ b/src/libsync/capabilities.h @@ -47,6 +47,9 @@ public: /// disable parallel upload in chunking bool chunkingParallelUploadDisabled() const; + /// Whether the "privatelink" DAV property is available + bool privateLinkPropertyAvailable() const; + /// returns true if the capabilities report notifications bool notificationsAvailable() const; -- 2.30.2