[Libreoffice-commits] online.git: loleaflet/src test/Makefile.am test/UnitWOPIRenameFile.cpp test/WopiTestServer.hpp wsd/ClientSession.cpp wsd/DocumentBroker.cpp wsd/DocumentBroker.hpp wsd/Storage.cpp wsd/Storage.hpp

Libreoffice Gerrit user logerrit at kemper.freedesktop.org
Mon May 13 19:30:37 UTC 2019


 loleaflet/src/control/Control.Toolbar.js |   10 +++
 loleaflet/src/control/Toolbar.js         |    8 ++
 loleaflet/src/core/Socket.js             |    5 +
 loleaflet/src/errormessages.js           |    3 -
 loleaflet/src/map/handler/Map.WOPI.js    |    4 +
 test/Makefile.am                         |    6 +-
 test/UnitWOPIRenameFile.cpp              |   90 +++++++++++++++++++++++++++++++
 test/WopiTestServer.hpp                  |   25 ++++++--
 wsd/ClientSession.cpp                    |   18 +++++-
 wsd/DocumentBroker.cpp                   |   33 ++++++++---
 wsd/DocumentBroker.hpp                   |    4 -
 wsd/Storage.cpp                          |   40 ++++++++-----
 wsd/Storage.hpp                          |   20 +++++-
 13 files changed, 224 insertions(+), 42 deletions(-)

New commits:
commit 0dbf9bcf27155fa846977bdf0b8e990e0bc07211
Author:     merttumer <mert.tumer at collabora.com>
AuthorDate: Tue Apr 30 17:21:44 2019 +0300
Commit:     merttumer <mert.tumer at collabora.com>
CommitDate: Mon May 13 22:29:40 2019 +0300

    Added RenameFile Support for WOPI
    
    This patch concerns rename file operation when the integration
    supports it
    Signed-off-by: merttumer <mert.tumer at collabora.com>
    
    Change-Id: Ibb4f615b91dda2491bfcd4d4738198d69eca4e6f
    Reviewed-on: https://gerrit.libreoffice.org/71587
    Reviewed-by: Jan Holesovsky <kendy at collabora.com>
    Tested-by: Jan Holesovsky <kendy at collabora.com>
    Signed-off-by: merttumer <mert.tumer at collabora.com>

diff --git a/loleaflet/src/control/Control.Toolbar.js b/loleaflet/src/control/Control.Toolbar.js
index 287a1598d..14a762315 100644
--- a/loleaflet/src/control/Control.Toolbar.js
+++ b/loleaflet/src/control/Control.Toolbar.js
@@ -1151,7 +1151,15 @@ function onSearchKeyDown(e) {
 function documentNameConfirm() {
 	var value = $('#document-name-input').val();
 	if (value !== null && value != '' && value != map['wopi'].BaseFileName) {
-		map.saveAs(value);
+		if (map['wopi'].UserCanRename && map['wopi'].SupportsRename) {
+			// file name must be without the extension
+			if (value.lastIndexOf('.') > 0)
+				value = value.substr(0, value.lastIndexOf('.'));
+			map.renameFile(value);
+		} else {
+			// saveAs for rename
+			map.saveAs(value);
+		}
 	}
 	map._onGotFocus();
 }
diff --git a/loleaflet/src/control/Toolbar.js b/loleaflet/src/control/Toolbar.js
index 3258e8b10..f75c0ae31 100644
--- a/loleaflet/src/control/Toolbar.js
+++ b/loleaflet/src/control/Toolbar.js
@@ -107,6 +107,14 @@ L.Map.include({
 			'options=' + options);
 	},
 
+	renameFile: function (filename) {
+		if (!filename) {
+			return;
+		}
+		this.showBusy(_('Renaming...'), false);
+		this._socket.sendMessage('renamefile filename=' + encodeURIComponent(filename));
+	},
+
 	applyStyle: function (style, familyName) {
 		if (!style || !familyName) {
 			this.fire('error', {cmd: 'setStyle', kind: 'incorrectparam'});
diff --git a/loleaflet/src/core/Socket.js b/loleaflet/src/core/Socket.js
index 7f45eb73a..3a3d8f269 100644
--- a/loleaflet/src/core/Socket.js
+++ b/loleaflet/src/core/Socket.js
@@ -433,6 +433,9 @@ L.Socket = L.Class.extend({
 			else if (command.errorKind === 'savefailed') {
 				storageError = errorMessages.storage.savefailed;
 			}
+			else if (command.errorKind === 'renamefailed') {
+				storageError = errorMessages.storage.renamefailed;
+			}
 			else if (command.errorKind === 'saveunauthorized') {
 				storageError = errorMessages.storage.saveunauthorized;
 			}
@@ -628,7 +631,7 @@ L.Socket = L.Class.extend({
 					', last: ' + (command.rendercount - this._map._docLayer._debugRenderCount));
 			this._map._docLayer._debugRenderCount = command.rendercount;
 		}
-		else if (textMsg.startsWith('saveas:')) {
+		else if (textMsg.startsWith('saveas:') || textMsg.startsWith('renamefile:')) {
 			this._map.hideBusy();
 			if (command !== undefined && command.url !== undefined && command.url !== '') {
 				this.close();
diff --git a/loleaflet/src/errormessages.js b/loleaflet/src/errormessages.js
index b4b04838f..7e790c833 100644
--- a/loleaflet/src/errormessages.js
+++ b/loleaflet/src/errormessages.js
@@ -28,7 +28,8 @@ errorMessages.storage = {
 	loadfailed: _('Failed to read document from storage. Please contact your storage server (%storageserver) administrator.'),
 	savediskfull: _('Save failed due to no disk space left on storage server. Document will now be read-only. Please contact the server (%storageserver) administrator to continue editing.'),
 	saveunauthorized: _('Document cannot be saved due to expired or invalid access token.'),
-	savefailed: _('Document cannot be saved. Check your permissions or contact the storage server administrator.')
+	savefailed: _('Document cannot be saved. Check your permissions or contact the storage server administrator.'),
+	renamefailed: _('Document cannot be renamed. Check your permissions or contact the storage server administrator.')
 };
 
 if (typeof window !== 'undefined') {
diff --git a/loleaflet/src/map/handler/Map.WOPI.js b/loleaflet/src/map/handler/Map.WOPI.js
index cf0bf7013..ff9200086 100644
--- a/loleaflet/src/map/handler/Map.WOPI.js
+++ b/loleaflet/src/map/handler/Map.WOPI.js
@@ -24,6 +24,8 @@ L.Map.WOPI = L.Handler.extend({
 	EnableShare: false,
 	HideUserList: null,
 	CallPythonScriptSource: null,
+	SupportsRename: false,
+	UserCanRename: false,
 
 	_appLoadedConditions: {
 		docloaded: false,
@@ -79,6 +81,8 @@ L.Map.WOPI = L.Handler.extend({
 		this.DisableInactiveMessages = !!wopiInfo['DisableInactiveMessages'];
 		this.UserCanNotWriteRelative = !!wopiInfo['UserCanNotWriteRelative'];
 		this.EnableInsertRemoteImage = !!wopiInfo['EnableInsertRemoteImage'];
+		this.SupportsRename = !!wopiInfo['SupportsRename'];
+		this.UserCanRename = !!wopiInfo['UserCanRename'];
 		this.EnableShare = !!wopiInfo['EnableShare'];
 		if (wopiInfo['HideUserList'])
 			this.HideUserList = wopiInfo['HideUserList'].split(',');
diff --git a/test/Makefile.am b/test/Makefile.am
index bd107629e..0432955ae 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -20,7 +20,7 @@ noinst_LTLIBRARIES = \
 	unit-fuzz.la unit-oob.la unit-oauth.la \
 	unit-wopi.la unit-wopi-saveas.la \
 	unit-wopi-ownertermination.la unit-wopi-versionrestore.la \
-	unit-wopi-documentconflict.la
+	unit-wopi-documentconflict.la unit_wopi_renamefile.la
 
 
 MAGIC_TO_FORCE_SHLIB_CREATION = -rpath /dummy
@@ -110,6 +110,8 @@ unit_wopi_versionrestore_la_SOURCES = UnitWOPIVersionRestore.cpp
 unit_wopi_versionrestore_la_LIBADD = $(CPPUNIT_LIBS)
 unit_wopi_documentconflict_la_SOURCES = UnitWOPIDocumentConflict.cpp
 unit_wopi_documentconflict_la_LIBADD = $(CPPUNIT_LIBS)
+unit_wopi_renamefile_la_SOURCES = UnitWOPIRenameFile.cpp
+unit_wopi_renamefile_la_LIBADD = $(CPPUNIT_LIBS)
 
 if HAVE_LO_PATH
 SYSTEM_STAMP = @SYSTEMPLATE_PATH@/system_stamp
@@ -128,7 +130,7 @@ check-local:
 TESTS = unit-typing.la unit-convert.la unit-prefork.la unit-tilecache.la \
 	unit-timeout.la unit-oauth.la unit-wopi.la unit-wopi-saveas.la \
         unit-wopi-ownertermination.la unit-wopi-versionrestore.la \
-        unit-wopi-documentconflict.la
+        unit-wopi-documentconflict.la unit_wopi_renamefile.la
 # TESTS = unit-client.la
 # TESTS += unit-admin.la
 # TESTS += unit-storage.la
diff --git a/test/UnitWOPIRenameFile.cpp b/test/UnitWOPIRenameFile.cpp
new file mode 100644
index 000000000..04d1fc6d6
--- /dev/null
+++ b/test/UnitWOPIRenameFile.cpp
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <config.h>
+
+#include <WopiTestServer.hpp>
+#include <Log.hpp>
+#include <Unit.hpp>
+#include <UnitHTTP.hpp>
+#include <helpers.hpp>
+#include <Poco/Net/HTTPRequest.h>
+#include <Poco/Util/LayeredConfiguration.h>
+
+class UnitWOPIRenameFile : public WopiTestServer
+{
+    enum class Phase
+    {
+        Load,
+        RenameFile,
+        Polling
+    } _phase;
+
+public:
+    UnitWOPIRenameFile() :
+        _phase(Phase::Load)
+    {
+    }
+
+    void assertRenameFileRequest(const Poco::Net::HTTPRequest& request) override
+    {
+        // spec says UTF-7...
+        CPPUNIT_ASSERT_EQUAL(std::string("hello"), request.get("X-WOPI-RequestedName"));
+    }
+
+    bool filterSendMessage(const char* data, const size_t len, const WSOpCode /* code */, const bool /* flush */, int& /*unitReturn*/) override
+    {
+        const std::string message(data, len);
+
+        const std::string expected("renamefile: filename=hello");
+        if (message.find(expected) == 0)
+        {
+            // successfully exit the test if we also got the outgoing message
+            // notifying about saving the file
+            exitTest(TestResult::Ok);
+        }
+
+        return false;
+    }
+
+    void invokeTest() override
+    {
+        constexpr char testName[] = "UnitWOPIRenameFile";
+
+        switch (_phase)
+        {
+            case Phase::Load:
+            {
+                initWebsocket("/wopi/files/0?access_token=anything");
+
+                helpers::sendTextFrame(*_ws->getLOOLWebSocket(), "load url=" + _wopiSrc, testName);
+                _phase = Phase::RenameFile;
+                break;
+            }
+            case Phase::RenameFile:
+            {
+                helpers::sendTextFrame(*_ws->getLOOLWebSocket(), "renamefile filename=hello", testName);
+                _phase = Phase::Polling;
+                break;
+            }
+            case Phase::Polling:
+            {
+                // just wait for the results
+                break;
+            }
+        }
+    }
+};
+
+UnitBase *unit_create_wsd(void)
+{
+    return new UnitWOPIRenameFile();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/test/WopiTestServer.hpp b/test/WopiTestServer.hpp
index ea5659912..237957ba2 100644
--- a/test/WopiTestServer.hpp
+++ b/test/WopiTestServer.hpp
@@ -92,6 +92,10 @@ public:
     {
     }
 
+    virtual void assertRenameFileRequest(const Poco::Net::HTTPRequest& /*request*/)
+    {
+    }
+
 protected:
     /// Here we act as a WOPI server, so that we have a server that responds to
     /// the wopi requests without additional expensive setup.
@@ -166,13 +170,22 @@ protected:
         else if (request.getMethod() == "POST" && (uriReq.getPath() == "/wopi/files/0" || uriReq.getPath() == "/wopi/files/1"))
         {
             LOG_INF("Fake wopi host request, handling PutRelativeFile: " << uriReq.getPath());
-
-            CPPUNIT_ASSERT_EQUAL(std::string("PUT_RELATIVE"), request.get("X-WOPI-Override"));
-
-            assertPutRelativeFileRequest(request);
-
             std::string wopiURL = helpers::getTestServerURI() + "/something wopi/files/1?access_token=anything";
-            std::string content = "{ \"Name\":\"hello world%1.pdf\", \"Url\":\"" + wopiURL + "\" }";
+            std::string content;
+
+            if(request.get("X-WOPI-Override") == std::string("PUT_RELATIVE"))
+            {
+                CPPUNIT_ASSERT_EQUAL(std::string("PUT_RELATIVE"), request.get("X-WOPI-Override"));
+                assertPutRelativeFileRequest(request);
+                content = "{ \"Name\":\"hello world%1.pdf\", \"Url\":\"" + wopiURL + "\" }";
+            }
+            else
+            {
+                // rename file; response should be the file name without the url and the extension
+                CPPUNIT_ASSERT_EQUAL(std::string("RENAME_FILE"), request.get("X-WOPI-Override"));
+                assertRenameFileRequest(request);
+                content = "{ \"Name\":\"hello\", \"Url\":\"" + wopiURL + "\" }";
+            }
 
             std::ostringstream oss;
             oss << "HTTP/1.1 200 OK\r\n"
diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp
index ad5d4b97a..2146cedfe 100644
--- a/wsd/ClientSession.cpp
+++ b/wsd/ClientSession.cpp
@@ -178,7 +178,8 @@ bool ClientSession::_handleInput(const char *buffer, int length)
              tokens[0] != "uploadsigneddocument" &&
              tokens[0] != "exportsignanduploaddocument" &&
              tokens[0] != "rendershapeselection" &&
-             tokens[0] != "removesession")
+             tokens[0] != "removesession" &&
+             tokens[0] != "renamefile")
     {
         sendTextFrame("error: cmd=" + tokens[0] + " kind=unknown");
         return false;
@@ -369,6 +370,19 @@ bool ClientSession::_handleInput(const char *buffer, int length)
         docBroker->broadcastMessage(firstLine);
         docBroker->removeSession(sessionId);
     }
+    else if (tokens[0] == "renamefile") {
+        std::string encodedWopiFilename;
+        if (!getTokenString(tokens[1], "filename", encodedWopiFilename))
+        {
+            LOG_ERR("Bad syntax for: " << firstLine);
+            sendTextFrame("error: cmd=renamefile kind=syntax");
+            return false;
+        }
+        std::string wopiFilename;
+        Poco::URI::decode(encodedWopiFilename, wopiFilename);
+        docBroker->saveAsToStorage(getId(), "", wopiFilename, true);
+        return true;
+    }
     else
     {
         if (tokens[0] == "key")
@@ -826,7 +840,7 @@ bool ClientSession::handleKitToClientMessage(const char* buffer, const int lengt
             {
                 // this also sends the saveas: result
                 LOG_TRC("Save-as path: " << resultURL.getPath());
-                docBroker->saveAsToStorage(getId(), resultURL.getPath(), wopiFilename);
+                docBroker->saveAsToStorage(getId(), resultURL.getPath(), wopiFilename, false);
             }
             else
                 sendTextFrame("error: cmd=storage kind=savefailed");
diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp
index 4b0390ce9..e3b42e8c3 100644
--- a/wsd/DocumentBroker.cpp
+++ b/wsd/DocumentBroker.cpp
@@ -561,6 +561,8 @@ bool DocumentBroker::load(const std::shared_ptr<ClientSession>& session, const s
         wopiInfo->set("EnableInsertRemoteImage", wopifileinfo->getEnableInsertRemoteImage());
         wopiInfo->set("EnableShare", wopifileinfo->getEnableShare());
         wopiInfo->set("HideUserList", wopifileinfo->getHideUserList());
+        wopiInfo->set("SupportsRename", wopifileinfo->getSupportsRename());
+        wopiInfo->set("UserCanRename", wopifileinfo->getUserCanRename());
         if (wopifileinfo->getHideChangeTrackingControls() != WopiStorage::WOPIFileInfo::TriState::Unset)
             wopiInfo->set("HideChangeTrackingControls", wopifileinfo->getHideChangeTrackingControls() == WopiStorage::WOPIFileInfo::TriState::True);
 
@@ -791,16 +793,16 @@ bool DocumentBroker::saveToStorage(const std::string& sessionId,
     return res;
 }
 
-bool DocumentBroker::saveAsToStorage(const std::string& sessionId, const std::string& saveAsPath, const std::string& saveAsFilename)
+bool DocumentBroker::saveAsToStorage(const std::string& sessionId, const std::string& saveAsPath, const std::string& saveAsFilename, const bool isRename)
 {
     assertCorrectThread();
 
-    return saveToStorageInternal(sessionId, true, "", saveAsPath, saveAsFilename);
+    return saveToStorageInternal(sessionId, true, "", saveAsPath, saveAsFilename, isRename);
 }
 
 bool DocumentBroker::saveToStorageInternal(const std::string& sessionId,
                                            bool success, const std::string& result,
-                                           const std::string& saveAsPath, const std::string& saveAsFilename)
+                                           const std::string& saveAsPath, const std::string& saveAsFilename, const bool isRename)
 {
     assertCorrectThread();
 
@@ -813,7 +815,7 @@ bool DocumentBroker::saveToStorageInternal(const std::string& sessionId,
     // notify the waiting thread, if any.
     LOG_TRC("Saving to storage docKey [" << _docKey << "] for session [" << sessionId <<
             "]. Success: " << success << ", result: " << result);
-    if (!success && result == "unmodified")
+    if (!success && result == "unmodified" && !isRename)
     {
         LOG_DBG("Save skipped as document [" << _docKey << "] was not modified.");
         _lastSaveTime = std::chrono::steady_clock::now();
@@ -850,7 +852,7 @@ bool DocumentBroker::saveToStorageInternal(const std::string& sessionId,
 
     // If the file timestamp hasn't changed, skip saving.
     const Poco::Timestamp newFileModifiedTime = Poco::File(_storage->getRootFilePath()).getLastModified();
-    if (!isSaveAs && newFileModifiedTime == _lastFileModifiedTime)
+    if (!isSaveAs && newFileModifiedTime == _lastFileModifiedTime && !isRename)
     {
         // Nothing to do.
         LOG_DBG("Skipping unnecessary saving to URI [" << uriAnonym << "] with docKey [" << _docKey <<
@@ -862,10 +864,10 @@ bool DocumentBroker::saveToStorageInternal(const std::string& sessionId,
     LOG_DBG("Persisting [" << _docKey << "] after saving to URI [" << uriAnonym << "].");
 
     assert(_storage && _tileCache);
-    StorageBase::SaveResult storageSaveResult = _storage->saveLocalFileToStorage(auth, saveAsPath, saveAsFilename);
+    StorageBase::SaveResult storageSaveResult = _storage->saveLocalFileToStorage(auth, saveAsPath, saveAsFilename, isRename);
     if (storageSaveResult.getResult() == StorageBase::SaveResult::OK)
     {
-        if (!isSaveAs)
+        if (!isSaveAs && !isRename)
         {
             // Saved and stored; update flags.
             setModified(false);
@@ -884,6 +886,19 @@ bool DocumentBroker::saveToStorageInternal(const std::string& sessionId,
             // Resume polling.
             _poll->wakeup();
         }
+        else if (isRename)
+        {
+            // encode the name
+            const std::string filename = storageSaveResult.getSaveAsName();
+            const std::string url = Poco::URI(storageSaveResult.getSaveAsUrl()).toString();
+            std::string encodedName;
+            Poco::URI::encode(filename, "", encodedName);
+            const std::string filenameAnonym = LOOLWSD::anonymizeUrl(filename);
+
+            std::ostringstream oss;
+            oss << "renamefile: " << "filename=" << encodedName << " url=" << url;
+            broadcastMessage(oss.str());
+        }
         else
         {
             // normalize the url (mainly to " " -> "%20")
@@ -931,7 +946,9 @@ bool DocumentBroker::saveToStorageInternal(const std::string& sessionId,
     {
         //TODO: Should we notify all clients?
         LOG_ERR("Failed to save docKey [" << _docKey << "] to URI [" << uriAnonym << "]. Notifying client.");
-        it->second->sendTextFrame("error: cmd=storage kind=savefailed");
+        std::ostringstream oss;
+        oss << "error: cmd=storage kind=" << (isRename ? "renamefailed" : "savefailed");
+        it->second->sendTextFrame(oss.str());
     }
     else if (storageSaveResult.getResult() == StorageBase::SaveResult::DOC_CHANGED)
     {
diff --git a/wsd/DocumentBroker.hpp b/wsd/DocumentBroker.hpp
index 5fd4cef48..0642599c2 100644
--- a/wsd/DocumentBroker.hpp
+++ b/wsd/DocumentBroker.hpp
@@ -246,7 +246,7 @@ public:
 
     /// Save As the document to Storage.
     /// @param saveAsPath Absolute path to the jailed file.
-    bool saveAsToStorage(const std::string& sesionId, const std::string& saveAsPath, const std::string& saveAsFilename);
+    bool saveAsToStorage(const std::string& sesionId, const std::string& saveAsPath, const std::string& saveAsFilename, const bool isRename);
 
     bool isModified() const { return _isModified; }
     void setModified(const bool value);
@@ -368,7 +368,7 @@ private:
     void terminateChild(const std::string& closeReason);
 
     /// Saves the doc to the storage.
-    bool saveToStorageInternal(const std::string& sesionId, bool success, const std::string& result = "", const std::string& saveAsPath = std::string(), const std::string& saveAsFilename = std::string());
+    bool saveToStorageInternal(const std::string& sesionId, bool success, const std::string& result = "", const std::string& saveAsPath = std::string(), const std::string& saveAsFilename = std::string(), const bool isRename = false);
 
     /// True iff a save is in progress (requested but not completed).
     bool isSaving() const { return _lastSaveResponseTime < _lastSaveRequestTime; }
diff --git a/wsd/Storage.cpp b/wsd/Storage.cpp
index 9d97cc1b6..d29bbaa63 100644
--- a/wsd/Storage.cpp
+++ b/wsd/Storage.cpp
@@ -336,7 +336,7 @@ std::string LocalStorage::loadStorageFileToLocal(const Authorization& /*auth*/)
 
 }
 
-StorageBase::SaveResult LocalStorage::saveLocalFileToStorage(const Authorization& /*auth*/, const std::string& /*saveAsPath*/, const std::string& /*saveAsFilename*/)
+StorageBase::SaveResult LocalStorage::saveLocalFileToStorage(const Authorization& /*auth*/, const std::string& /*saveAsPath*/, const std::string& /*saveAsFilename*/, bool /*isRename*/)
 {
     try
     {
@@ -499,6 +499,8 @@ std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(const Au
     bool userCanNotWriteRelative = true;
     bool enableInsertRemoteImage = false;
     bool enableShare = false;
+    bool supportsRename = false;
+    bool userCanRename = false;
     std::string hideUserList("false");
     WOPIFileInfo::TriState disableChangeTrackingRecord = WOPIFileInfo::TriState::Unset;
     WOPIFileInfo::TriState disableChangeTrackingShow = WOPIFileInfo::TriState::Unset;
@@ -585,6 +587,8 @@ std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(const Au
         JsonUtil::findJSONValue(object, "EnableInsertRemoteImage", enableInsertRemoteImage);
         JsonUtil::findJSONValue(object, "EnableShare", enableShare);
         JsonUtil::findJSONValue(object, "HideUserList", hideUserList);
+        JsonUtil::findJSONValue(object, "SupportsRename", supportsRename);
+        JsonUtil::findJSONValue(object, "UserCanRename", userCanRename);
         bool booleanFlag = false;
         if (JsonUtil::findJSONValue(object, "DisableChangeTrackingRecord", booleanFlag))
             disableChangeTrackingRecord = (booleanFlag ? WOPIFileInfo::TriState::True : WOPIFileInfo::TriState::False);
@@ -614,7 +618,7 @@ std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(const Au
          enableOwnerTermination, disablePrint, disableExport, disableCopy,
          disableInactiveMessages, userCanNotWriteRelative, enableInsertRemoteImage, enableShare,
          hideUserList, disableChangeTrackingShow, disableChangeTrackingRecord,
-         hideChangeTrackingControls, callDuration}));
+         hideChangeTrackingControls, supportsRename, userCanRename, callDuration}));
 }
 
 /// uri format: http://server/<...>/wopi*/files/<id>/content
@@ -692,7 +696,7 @@ std::string WopiStorage::loadStorageFileToLocal(const Authorization& auth)
     return "";
 }
 
-StorageBase::SaveResult WopiStorage::saveLocalFileToStorage(const Authorization& auth, const std::string& saveAsPath, const std::string& saveAsFilename)
+StorageBase::SaveResult WopiStorage::saveLocalFileToStorage(const Authorization& auth, const std::string& saveAsPath, const std::string& saveAsFilename, const bool isRename)
 {
     // TODO: Check if this URI has write permission (canWrite = true)
 
@@ -703,7 +707,7 @@ StorageBase::SaveResult WopiStorage::saveLocalFileToStorage(const Authorization&
     const size_t size = getFileSize(filePath);
 
     Poco::URI uriObject(getUri());
-    uriObject.setPath(isSaveAs? uriObject.getPath(): uriObject.getPath() + "/contents");
+    uriObject.setPath(isSaveAs || isRename? uriObject.getPath(): uriObject.getPath() + "/contents");
     auth.authorizeURI(uriObject);
     const std::string uriAnonym = LOOLWSD::anonymizeUrl(uriObject.toString());
 
@@ -718,7 +722,7 @@ StorageBase::SaveResult WopiStorage::saveLocalFileToStorage(const Authorization&
         request.set("User-Agent", WOPI_AGENT_STRING);
         auth.authorizeRequest(request);
 
-        if (!isSaveAs)
+        if (!isSaveAs && !isRename)
         {
             // normal save
             request.set("X-WOPI-Override", "PUT");
@@ -736,9 +740,6 @@ StorageBase::SaveResult WopiStorage::saveLocalFileToStorage(const Authorization&
         }
         else
         {
-            // save as
-            request.set("X-WOPI-Override", "PUT_RELATIVE");
-
             // the suggested target has to be in UTF-7; default to extension
             // only when the conversion fails
             std::string suggestedTarget = "." + Poco::Path(saveAsFilename).getExtension();
@@ -766,9 +767,19 @@ StorageBase::SaveResult WopiStorage::saveLocalFileToStorage(const Authorization&
                 }
             }
 
-            request.set("X-WOPI-SuggestedTarget", suggestedTarget);
-
-            request.set("X-WOPI-Size", std::to_string(size));
+            if (isRename)
+            {
+                // rename file
+                request.set("X-WOPI-Override", "RENAME_FILE");
+                request.set("X-WOPI-RequestedName", suggestedTarget);
+            }
+            else
+            {
+                // save as
+                request.set("X-WOPI-Override", "PUT_RELATIVE");
+                request.set("X-WOPI-Size", std::to_string(size));
+                request.set("X-WOPI-SuggestedTarget", suggestedTarget);
+            }
         }
 
         request.setContentType("application/octet-stream");
@@ -786,7 +797,7 @@ StorageBase::SaveResult WopiStorage::saveLocalFileToStorage(const Authorization&
         Poco::StreamCopier::copyStream(rs, oss);
         std::string responseString = oss.str();
 
-        const std::string wopiLog(isSaveAs ? "WOPI::PutRelativeFile" : "WOPI::PutFile");
+        const std::string wopiLog(isSaveAs ? "WOPI::PutRelativeFile" : (isRename ? "WOPI::RenameFile":"WOPI::PutFile"));
 
         if (Log::infoEnabled())
         {
@@ -834,7 +845,7 @@ StorageBase::SaveResult WopiStorage::saveLocalFileToStorage(const Authorization&
                 LOG_TRC(wopiLog << " returns LastModifiedTime [" << lastModifiedTime << "].");
                 getFileInfo().setModifiedTime(iso8601ToTimestamp(lastModifiedTime, "LastModifiedTime"));
 
-                if (isSaveAs)
+                if (isSaveAs || isRename)
                 {
                     const std::string name = JsonUtil::getJSONValue<std::string>(object, "Name");
                     LOG_TRC(wopiLog << " returns Name [" << LOOLWSD::anonymizeUrl(name) << "].");
@@ -844,7 +855,6 @@ StorageBase::SaveResult WopiStorage::saveLocalFileToStorage(const Authorization&
 
                     saveResult.setSaveAsResult(name, url);
                 }
-
                 // Reset the force save flag now, if any, since we are done saving
                 // Next saves shouldn't be saved forcefully unless commanded
                 forceSave(false);
@@ -897,7 +907,7 @@ std::string WebDAVStorage::loadStorageFileToLocal(const Authorization& /*auth*/)
     return getUri().toString();
 }
 
-StorageBase::SaveResult WebDAVStorage::saveLocalFileToStorage(const Authorization& /*auth*/, const std::string& /*saveAsPath*/, const std::string& /*saveAsFilename*/)
+StorageBase::SaveResult WebDAVStorage::saveLocalFileToStorage(const Authorization& /*auth*/, const std::string& /*saveAsPath*/, const std::string& /*saveAsFilename*/, bool /*isRename*/)
 {
     // TODO: implement webdav PUT.
     return StorageBase::SaveResult(StorageBase::SaveResult::OK);
diff --git a/wsd/Storage.hpp b/wsd/Storage.hpp
index bf228721a..02539b8f1 100644
--- a/wsd/Storage.hpp
+++ b/wsd/Storage.hpp
@@ -193,7 +193,7 @@ public:
 
     /// Writes the contents of the file back to the source.
     /// @param savedFile When the operation was saveAs, this is the path to the file that was saved.
-    virtual SaveResult saveLocalFileToStorage(const Authorization& auth, const std::string& saveAsPath, const std::string& saveAsFilename) = 0;
+    virtual SaveResult saveLocalFileToStorage(const Authorization& auth, const std::string& saveAsPath, const std::string& saveAsFilename, const bool isRename) = 0;
 
     static size_t getFileSize(const std::string& filename);
 
@@ -274,7 +274,7 @@ public:
 
     std::string loadStorageFileToLocal(const Authorization& auth) override;
 
-    SaveResult saveLocalFileToStorage(const Authorization& auth, const std::string& saveAsPath, const std::string& saveAsFilename) override;
+    SaveResult saveLocalFileToStorage(const Authorization& auth, const std::string& saveAsPath, const std::string& saveAsFilename, const bool isRename) override;
 
 private:
     /// True if the jailed file is not linked but copied.
@@ -329,6 +329,8 @@ public:
                      const TriState disableChangeTrackingShow,
                      const TriState disableChangeTrackingRecord,
                      const TriState hideChangeTrackingControls,
+                     const bool supportsRename,
+                     const bool userCanRename,
                      const std::chrono::duration<double> callDuration)
             : _userId(userid),
               _obfuscatedUserId(obfuscatedUserId),
@@ -352,6 +354,8 @@ public:
               _disableChangeTrackingShow(disableChangeTrackingShow),
               _disableChangeTrackingRecord(disableChangeTrackingRecord),
               _hideChangeTrackingControls(hideChangeTrackingControls),
+              _supportsRename(supportsRename),
+              _userCanRename(userCanRename),
               _callDuration(callDuration)
             {
                 _userExtraInfo = userExtraInfo;
@@ -397,6 +401,10 @@ public:
 
         bool getEnableShare() const { return _enableShare; }
 
+        bool getSupportsRename() const { return _supportsRename; }
+
+        bool getUserCanRename() const { return _userCanRename; }
+
         std::string& getHideUserList() { return _hideUserList; }
 
         TriState getDisableChangeTrackingShow() const { return _disableChangeTrackingShow; }
@@ -456,6 +464,10 @@ public:
         TriState _disableChangeTrackingRecord;
         /// If we should hide change-tracking commands for this user.
         TriState _hideChangeTrackingControls;
+        /// If WOPI host supports rename
+        bool _supportsRename;
+        /// If user is allowed to rename the document
+        bool _userCanRename;
 
         /// Time it took to call WOPI's CheckFileInfo
         std::chrono::duration<double> _callDuration;
@@ -470,7 +482,7 @@ public:
     /// uri format: http://server/<...>/wopi*/files/<id>/content
     std::string loadStorageFileToLocal(const Authorization& auth) override;
 
-    SaveResult saveLocalFileToStorage(const Authorization& auth, const std::string& saveAsPath, const std::string& saveAsFilename) override;
+    SaveResult saveLocalFileToStorage(const Authorization& auth, const std::string& saveAsPath, const std::string& saveAsFilename, const bool isRename) override;
 
     /// Total time taken for making WOPI calls during load
     std::chrono::duration<double> getWopiLoadDuration() const { return _wopiLoadDuration; }
@@ -500,7 +512,7 @@ public:
 
     std::string loadStorageFileToLocal(const Authorization& auth) override;
 
-    SaveResult saveLocalFileToStorage(const Authorization& auth, const std::string& saveAsPath, const std::string& saveAsFilename) override;
+    SaveResult saveLocalFileToStorage(const Authorization& auth, const std::string& saveAsPath, const std::string& saveAsFilename, const bool isRename) override;
 
 private:
     std::unique_ptr<AuthBase> _authAgent;


More information about the Libreoffice-commits mailing list