loleaflet/js/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                 |   17 +++++-
 wsd/DocumentBroker.cpp                |   33 +++++++++---
 wsd/DocumentBroker.hpp                |    4 -
 wsd/Storage.cpp                       |   40 +++++++++------
 wsd/Storage.hpp                       |   20 ++++++-
 13 files changed, 223 insertions(+), 42 deletions(-)

New commits:
commit c93c8c86892ab5743929a17fef6a8a0ab0cc0518
Author:     merttumer <mert.tu...@collabora.com>
AuthorDate: Tue Apr 30 17:21:44 2019 +0300
Commit:     Jan Holesovsky <ke...@collabora.com>
CommitDate: Mon May 6 20:35:46 2019 +0200

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

diff --git a/loleaflet/js/toolbar.js b/loleaflet/js/toolbar.js
index 9c9863595..e0916cdd3 100644
--- a/loleaflet/js/toolbar.js
+++ b/loleaflet/js/toolbar.js
@@ -1334,7 +1334,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 8f3820431..a08642b2e 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 0e27a7096..884a0c8d2 100644
--- a/loleaflet/src/core/Socket.js
+++ b/loleaflet/src/core/Socket.js
@@ -493,6 +493,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;
                        }
@@ -696,7 +699,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 c43e14dd6..bf6d5a473 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
@@ -106,6 +106,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
@@ -123,7 +125,7 @@ check-local:
 TESTS = 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 3e95c3b8b..b71212f0f 100644
--- a/test/WopiTestServer.hpp
+++ b/test/WopiTestServer.hpp
@@ -86,6 +86,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.
@@ -160,13 +164,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 c7e99f7fb..3ba3580e7 100644
--- a/wsd/ClientSession.cpp
+++ b/wsd/ClientSession.cpp
@@ -182,7 +182,7 @@ bool ClientSession::_handleInput(const char *buffer, int 
length)
              tokens[0] != "exportsignanduploaddocument" &&
              tokens[0] != "rendershapeselection" &&
              tokens[0] != "removesession" &&
-             tokens[0] != "resizewindow")
+             tokens[0] != "renamefile")
     {
         LOG_ERR("Session [" << getId() << "] got unknown command [" << 
tokens[0] << "].");
         sendTextFrame("error: cmd=" + tokens[0] + " kind=unknown");
@@ -410,6 +410,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")
@@ -883,7 +896,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 191c76ce8..744d4ec87 100644
--- a/wsd/DocumentBroker.cpp
+++ b/wsd/DocumentBroker.cpp
@@ -573,6 +573,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);
 
@@ -795,16 +797,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();
 
@@ -817,7 +819,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();
@@ -854,7 +856,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 <<
@@ -866,10 +868,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);
@@ -889,6 +891,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")
@@ -936,7 +951,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 eeeb97da3..e470d0c8c 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);
@@ -370,7 +370,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 0f04c91b8..cfe27c49d 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
     {
@@ -500,6 +500,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;
@@ -583,6 +585,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);
@@ -612,7 +616,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
@@ -690,7 +694,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)
 
@@ -701,7 +705,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());
 
@@ -716,7 +720,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");
@@ -734,9 +738,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();
@@ -764,9 +765,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");
@@ -784,7 +795,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())
         {
@@ -832,7 +843,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) << "].");
@@ -842,7 +853,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);
@@ -895,7 +905,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;
_______________________________________________
Libreoffice-commits mailing list
libreoffice-comm...@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits

Reply via email to