Private links: Retrieve link through propfind property #6020
authorChristian Kamm <mail@ckamm.de>
Fri, 15 Sep 2017 12:24:34 +0000 (14:24 +0200)
committerRoeland Jago Douma <roeland@famdouma.nl>
Thu, 5 Oct 2017 20:01:34 +0000 (22:01 +0200)
* 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
src/gui/sharedialog.cpp
src/gui/sharedialog.h
src/gui/shareusergroupwidget.cpp
src/gui/shareusergroupwidget.h
src/gui/socketapi.cpp
src/gui/socketapi.h
src/libsync/account.cpp
src/libsync/account.h
src/libsync/capabilities.cpp
src/libsync/capabilities.h

index ca8430469f1db3ae1327970f129347668a9d6c6b..ba1c7ced0097329ada758d9b7206f8c8cfd82ff7 100644 (file)
@@ -37,7 +37,7 @@ class OCSYNC_EXPORT SyncJournalFileRecord
 public:
     SyncJournalFileRecord();
 
-    bool isValid()
+    bool isValid() const
     {
         return !_path.isEmpty();
     }
index 24a912fdc2d8756c4d07ec92f90a9c93dd76e070..aa47598e92ebc70078cde82258ea39fbdc8ad2c0 100644 (file)
@@ -46,7 +46,7 @@ ShareDialog::ShareDialog(QPointer<AccountState> 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> 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<QByteArray>() << "http://open-collaboration-services.org/ns:share-permissions");
+    job->setProperties(
+        QList<QByteArray>()
+        << "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<SharePermissions>(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();
     }
index 50aeea2e1704e20bf7cf09b9b8a800fbb402787d..8af3f3fff8e30aced6207ec343086e1dfee71911 100644 (file)
@@ -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;
index 8cecac2a42fdec51c6b744872b463df32183769f..5b505f21a34220f92d8a4b6b13c84dbf5b7fc451 100644 (file)
@@ -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);
 }
 
index fbc2bdfa4d1f00589242de9bbd7fab70b85408d8..1feca5f262400be218106f75e03a6c610696acfb 100644 (file)
@@ -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;
index 9261cef83868ce11b20f740471b2a62886acb662..0073e6154b1663e7a0b62d0d681d8d3325cb9a72 100644 (file)
@@ -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<QByteArray>() << "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
index 2b7b4fe9f2f7aa503f8e5fa424fe05bff0b1ff4c..25eb3c72ddb25660035b56265662fdfca5ad839b 100644 (file)
@@ -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<QString> _registeredAliases;
     QList<SocketListener> _listeners;
index 7f2fadb29351c25fc433f081721caea5433ea99c..61a99c0655d5efd0f1eca31010b5c36536f7fe54 100644 (file)
@@ -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)));
index c9f361feb8913e87658fa1ebd17679562592ace7..4f300ce975b3c953cec74ad8de2d3383dd2810a5 100644 (file)
@@ -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;
index 4722c0b99bf4ad5694adf323b935b579a3e97958..4dfc33b89620d9a65da9d57e3ecbd59c96283bc8 100644 (file)
@@ -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<int> Capabilities::httpErrorCodesThatResetFailingChunkedUploads() const
 {
     QList<int> list;
index 6baaaa0ec7f339f7255ee00ae51eaa6d126baf02..9b15edb9c878beb7cf8b10ea6d31834977bf39d0 100644 (file)
@@ -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;