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