kit/ChildSession.cpp | 33 +++++++++++++-- loleaflet/reference.html | 9 +--- loleaflet/src/map/handler/Map.WOPI.js | 6 +- test/Makefile.am | 6 +- test/UnitOAuth.cpp | 5 -- test/UnitWOPI.cpp | 10 ---- test/UnitWOPISaveAs.cpp | 72 ++++++++++++++++++++++++++++++++++ test/WopiTestServer.hpp | 61 ++++++++++++++++++++++++---- wsd/ClientSession.cpp | 50 +++++++++++++++++------ wsd/DocumentBroker.cpp | 50 ++++++++++++++++------- wsd/DocumentBroker.hpp | 7 ++- wsd/Storage.cpp | 56 +++++++++++++++++--------- wsd/Storage.hpp | 9 ++-- 13 files changed, 282 insertions(+), 92 deletions(-)
New commits: commit 977c84c798b51f7acf18337a34471bd26cadf55f Author: Jan Holesovsky <ke...@collabora.com> Date: Fri Oct 20 18:12:05 2017 +0200 tdf#99744 SaveAs: Reimplementation of the PutRelativeFile going through Kit. This is necessary so that changing of the file type works. Includes a unit test. Change-Id: Id01d44e555b6bac1002ff950de461fd330602f63 Reviewed-on: https://gerrit.libreoffice.org/43617 Reviewed-by: pranavk <pran...@collabora.co.uk> Tested-by: pranavk <pran...@collabora.co.uk> diff --git a/kit/ChildSession.cpp b/kit/ChildSession.cpp index 633f6b42..ad43c8f0 100644 --- a/kit/ChildSession.cpp +++ b/kit/ChildSession.cpp @@ -892,18 +892,37 @@ bool ChildSession::resetSelection(const char* /*buffer*/, int /*length*/, const bool ChildSession::saveAs(const char* /*buffer*/, int /*length*/, const std::vector<std::string>& tokens) { - std::string url, format, filterOptions; + std::string wopiFilename, url, format, filterOptions; - if (tokens.size() < 4 || + if (tokens.size() <= 1 || !getTokenString(tokens[1], "url", url)) { sendTextFrame("error: cmd=saveas kind=syntax"); return false; } - getTokenString(tokens[2], "format", format); + // if the url is a 'wopi:///something/blah.odt', then save to a temporary + Poco::URI wopiURL(url); + if (wopiURL.getScheme() == "wopi") + { + std::vector<std::string> pathSegments; + wopiURL.getPathSegments(pathSegments); + + if (pathSegments.size() == 0) + { + sendTextFrame("error: cmd=saveas kind=syntax"); + return false; + } - if (getTokenString(tokens[3], "options", filterOptions)) + // TODO do we need a tempdir here? + url = std::string("file://") + JAILED_DOCUMENT_ROOT + pathSegments[pathSegments.size() - 1]; + wopiFilename = wopiURL.getPath(); + } + + if (tokens.size() > 2) + getTokenString(tokens[2], "format", format); + + if (tokens.size() > 3 && getTokenString(tokens[3], "options", filterOptions)) { if (tokens.size() > 4) { @@ -922,7 +941,11 @@ bool ChildSession::saveAs(const char* /*buffer*/, int /*length*/, const std::vec filterOptions.size() == 0 ? nullptr : filterOptions.c_str()); } - sendTextFrame("saveas: url=" + url); + std::string encodedURL, encodedWopiFilename; + Poco::URI::encode(url, "", encodedURL); + Poco::URI::encode(wopiFilename, "", encodedWopiFilename); + + sendTextFrame("saveas: url=" + encodedURL + " wopifilename=" + encodedWopiFilename); std::string successStr = success ? "true" : "false"; sendTextFrame("unocommandresult: {" "\"commandName\":\"saveas\"," diff --git a/loleaflet/reference.html b/loleaflet/reference.html index f87fa773..52cbccfb 100644 --- a/loleaflet/reference.html +++ b/loleaflet/reference.html @@ -2915,13 +2915,10 @@ WOPI host to editor <tr> <td><code><b>Action_SaveAs</b></code></td> <td><code> - <nobr>Name: <String></nobr> - <nobr>Path: <String></nobr> + <nobr>Filename: <String></nobr> </code></td> - <td>Creates copy of the document with given Name.<br/> - <code>Name</code> is the requested name for the new file.<br/> - <code>Path</code> is the relative path in the WOPI host file system where the - user wants the new file to be saved.<br/> + <td>Creates copy of the document with given Filename.<br/> + <code>Filename</code> is the requested filename for the new file.<br/> </td> </tr> <tr> diff --git a/loleaflet/src/map/handler/Map.WOPI.js b/loleaflet/src/map/handler/Map.WOPI.js index 8aa364c1..560be0c8 100644 --- a/loleaflet/src/map/handler/Map.WOPI.js +++ b/loleaflet/src/map/handler/Map.WOPI.js @@ -213,11 +213,11 @@ L.Map.WOPI = L.Handler.extend({ else if (msg.MessageId === 'Action_SaveAs') { /* TODO if (msg.Values) { - if (msg.Values.name === null || msg.Values.name === undefined) { - msg.Values.name = ''; + if (msg.Values.Filename === null || msg.Values.Filename === undefined) { + msg.Values.Filename = ''; } this.showBusy(_('Creating copy...'), false); - map.saveAs(msg.Values.name, msg.Values.path); + map.saveAs(msg.Values.Filename); } */ } diff --git a/test/Makefile.am b/test/Makefile.am index d834dcb2..8c46facd 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -17,7 +17,7 @@ noinst_LTLIBRARIES = \ unit-storage.la \ unit-admin.la unit-tilecache.la \ unit-fuzz.la unit-oob.la unit-oauth.la \ - unit-wopi.la + unit-wopi.la unit-wopi-saveas.la MAGIC_TO_FORCE_SHLIB_CREATION = -rpath /dummy AM_LDFLAGS = -pthread -module $(MAGIC_TO_FORCE_SHLIB_CREATION) $(ZLIB_LIBS) @@ -66,6 +66,8 @@ unit_oauth_la_SOURCES = UnitOAuth.cpp unit_oauth_la_LIBADD = $(CPPUNIT_LIBS) unit_wopi_la_SOURCES = UnitWOPI.cpp unit_wopi_la_LIBADD = $(CPPUNIT_LIBS) +unit_wopi_saveas_la_SOURCES = UnitWOPISaveAs.cpp +unit_wopi_saveas_la_LIBADD = $(CPPUNIT_LIBS) if HAVE_LO_PATH SYSTEM_STAMP = @SYSTEMPLATE_PATH@/system_stamp @@ -79,7 +81,7 @@ check-local: ./run_unit.sh --log-file test.log --trs-file test.trs # FIXME 2: unit-oob.la fails with symbol undefined: # UnitWSD::testHandleRequest(UnitWSD::TestRequest, UnitHTTPServerRequest&, UnitHTTPServerResponse&) , -TESTS = unit-prefork.la unit-tilecache.la unit-timeout.la unit-oauth.la unit-wopi.la # unit-storage.la # unit-admin.la +TESTS = unit-prefork.la unit-tilecache.la unit-timeout.la unit-oauth.la unit-wopi.la unit-wopi-saveas.la # unit-storage.la # unit-admin.la else TESTS = ${top_builddir}/test/test endif diff --git a/test/UnitOAuth.cpp b/test/UnitOAuth.cpp index f88a4b55..5b2b09c2 100644 --- a/test/UnitOAuth.cpp +++ b/test/UnitOAuth.cpp @@ -87,11 +87,6 @@ public: exitTest(TestResult::Ok); } - void assertPutFileRequest(const Poco::Net::HTTPRequest& /*request*/) override - { - // nothing to assert - } - void invokeTest() override { constexpr char testName[] = "UnitOAuth"; diff --git a/test/UnitWOPI.cpp b/test/UnitWOPI.cpp index a8808d20..338fb145 100644 --- a/test/UnitWOPI.cpp +++ b/test/UnitWOPI.cpp @@ -50,16 +50,6 @@ public: return _savingPhase == SavingPhase::Modified; } - void assertCheckFileInfoRequest(const Poco::Net::HTTPRequest& /*request*/) override - { - // nothing to assert in CheckFileInfo - } - - void assertGetFileRequest(const Poco::Net::HTTPRequest& /*request*/) override - { - // nothing to assert in GetFile - } - void assertPutFileRequest(const Poco::Net::HTTPRequest& request) override { if (_savingPhase == SavingPhase::Unmodified) diff --git a/test/UnitWOPISaveAs.cpp b/test/UnitWOPISaveAs.cpp new file mode 100644 index 00000000..4bd3e9e0 --- /dev/null +++ b/test/UnitWOPISaveAs.cpp @@ -0,0 +1,72 @@ +/* -*- 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 UnitWOPISaveAs : public WopiTestServer +{ + enum class Phase + { + LoadAndSaveAs, + Polling + } _phase; + +public: + UnitWOPISaveAs() : + _phase(Phase::LoadAndSaveAs) + { + } + + void assertPutFileRelativeRequest(const Poco::Net::HTTPRequest& request) override + { + CPPUNIT_ASSERT_EQUAL(std::string("/path/to/hello world.txt"), request.get("X-WOPI-RelativeTarget")); + + exitTest(TestResult::Ok); + } + + void invokeTest() override + { + constexpr char testName[] = "UnitWOPISaveAs"; + + switch (_phase) + { + case Phase::LoadAndSaveAs: + { + initWebsocket("/wopi/files/0?access_token=anything"); + + helpers::sendTextFrame(*_ws->getLOOLWebSocket(), "load url=" + _wopiSrc, testName); + helpers::sendTextFrame(*_ws->getLOOLWebSocket(), "saveas url=wopi:///path/to/hello%20world.txt", testName); + SocketPoll::wakeupWorld(); + + _phase = Phase::Polling; + break; + } + case Phase::Polling: + { + // just wait for the results + break; + } + } + } +}; + +UnitBase *unit_create_wsd(void) +{ + return new UnitWOPISaveAs(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/test/WopiTestServer.hpp b/test/WopiTestServer.hpp index a3851b5d..6324114d 100644 --- a/test/WopiTestServer.hpp +++ b/test/WopiTestServer.hpp @@ -28,8 +28,13 @@ protected: /// Websocket to communicate. std::unique_ptr<UnitWebSocket> _ws; + /// Content of the file. + std::string _fileContent; + public: - WopiTestServer() : UnitWSD() + WopiTestServer(std::string fileContent = "Hello, world") + : UnitWSD() + , _fileContent(fileContent) { } @@ -47,24 +52,32 @@ public: assert(_ws.get()); } - virtual void assertCheckFileInfoRequest(const Poco::Net::HTTPRequest& request) = 0; + virtual void assertCheckFileInfoRequest(const Poco::Net::HTTPRequest& /*request*/) + { + } - virtual void assertGetFileRequest(const Poco::Net::HTTPRequest& request) = 0; + virtual void assertGetFileRequest(const Poco::Net::HTTPRequest& /*request*/) + { + } + + virtual void assertPutFileRequest(const Poco::Net::HTTPRequest& /*request*/) + { + } - virtual void assertPutFileRequest(const Poco::Net::HTTPRequest& request) = 0; + virtual void assertPutFileRelativeRequest(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. virtual bool handleHttpRequest(const Poco::Net::HTTPRequest& request, std::shared_ptr<StreamSocket>& socket) override { - static const std::string hello("Hello, world"); - Poco::URI uriReq(request.getURI()); LOG_INF("Fake wopi host request: " << uriReq.toString()); // CheckFileInfo - if (uriReq.getPath() == "/wopi/files/0" || uriReq.getPath() == "/wopi/files/1") + if (request.getMethod() == "GET" && (uriReq.getPath() == "/wopi/files/0" || uriReq.getPath() == "/wopi/files/1")) { LOG_INF("Fake wopi host request, handling CheckFileInfo: " << uriReq.getPath()); @@ -73,7 +86,7 @@ protected: Poco::LocalDateTime now; Poco::JSON::Object::Ptr fileInfo = new Poco::JSON::Object(); fileInfo->set("BaseFileName", "hello.txt"); - fileInfo->set("Size", hello.size()); + fileInfo->set("Size", _fileContent.size()); fileInfo->set("Version", "1.0"); fileInfo->set("OwnerId", "test"); fileInfo->set("UserId", "test"); @@ -115,10 +128,38 @@ protected: oss << "HTTP/1.1 200 OK\r\n" << "Last-Modified: " << Poco::DateTimeFormatter::format(Poco::Timestamp(), Poco::DateTimeFormat::HTTP_FORMAT) << "\r\n" << "User-Agent: " << WOPI_AGENT_STRING << "\r\n" - << "Content-Length: " << hello.size() << "\r\n" + << "Content-Length: " << _fileContent.size() << "\r\n" << "Content-Type: " << mimeType << "\r\n" << "\r\n" - << hello; + << _fileContent; + + socket->send(oss.str()); + socket->shutdown(); + + return true; + } + else if (request.getMethod() == "POST" && (uriReq.getPath() == "/wopi/files/0" || uriReq.getPath() == "/wopi/files/1")) + { + LOG_INF("Fake wopi host request, handling PutFileRelative: " << uriReq.getPath()); + + CPPUNIT_ASSERT_EQUAL(std::string("PUT_RELATIVE"), request.get("X-WOPI-Override")); + + assertPutFileRelativeRequest(request); + + Poco::URI wopiURL(helpers::getTestServerURI() + "/wopi/files/1"); + std::string url; + Poco::URI::encode(wopiURL.toString(), ":/?", url); + + std::string content = "{ \"Name\":\"hello world.txt\", \"Url\":\"" + url + "\" }"; + + std::ostringstream oss; + oss << "HTTP/1.1 200 OK\r\n" + << "Last-Modified: " << Poco::DateTimeFormatter::format(Poco::Timestamp(), Poco::DateTimeFormat::HTTP_FORMAT) << "\r\n" + << "User-Agent: " << WOPI_AGENT_STRING << "\r\n" + << "Content-Length: " << content.size() << "\r\n" + << "Content-Type: application/json\r\n" + << "\r\n" + << content; socket->send(oss.str()); socket->shutdown(); diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp index b5e78b7e..5bd105ad 100644 --- a/wsd/ClientSession.cpp +++ b/wsd/ClientSession.cpp @@ -616,38 +616,64 @@ bool ClientSession::handleKitToClientMessage(const char* buffer, const int lengt int curPart; return getTokenInteger(tokens[1], "part", curPart); } - else if (tokens.size() == 2 && tokens[0] == "saveas:") + else if (tokens.size() == 3 && tokens[0] == "saveas:") { - std::string url; - if (!getTokenString(tokens[1], "url", url)) + std::string encodedURL; + if (!getTokenString(tokens[1], "url", encodedURL)) { LOG_ERR("Bad syntax for: " << firstLine); return false; } + std::string encodedWopiFilename; + if (!getTokenString(tokens[2], "wopifilename", encodedWopiFilename)) + { + LOG_ERR("Bad syntax for: " << firstLine); + return false; + } + + std::string url, wopiFilename; + Poco::URI::decode(encodedURL, url); + Poco::URI::decode(encodedWopiFilename, wopiFilename); + // Save-as completed, inform the ClientSession. - const std::string filePrefix("file:///"); - if (url.find(filePrefix) == 0) + Poco::URI resultURL(url); + if (resultURL.getScheme() == "file") { + std::string relative(resultURL.getPath()); + if (relative.size() > 0 && relative[0] == '/') + relative = relative.substr(1); + // Rewrite file:// URLs, as they are visible to the outside world. - const Path path(docBroker->getJailRoot(), url.substr(filePrefix.length())); + const Path path(docBroker->getJailRoot(), relative); if (Poco::File(path).exists()) { - url = filePrefix + path.toString().substr(1); + resultURL.setPath(path.toString()); } else { // Blank for failure. - LOG_DBG("SaveAs produced no output, producing blank url."); - url.clear(); + LOG_DBG("SaveAs produced no output in '" << path.toString() << "', producing blank url."); + resultURL.clear(); } } - if (_saveAsSocket) + LOG_TRC("Save-as URL: " << resultURL.toString()); + + if (!_saveAsSocket) { - Poco::URI resultURL(url); - LOG_TRC("Save-as URL: " << resultURL.toString()); + // Normal SaveAs - save to Storage and log result. + if (resultURL.getScheme() == "file" && !resultURL.getPath().empty()) + { + docBroker->saveAsToStorage(getId(), resultURL.getPath(), wopiFilename); + } + if (!isCloseFrame()) + forwardToClient(payload); + } + else + { + // using the convert-to REST API // TODO: Send back error when there is no output. if (!resultURL.getPath().empty()) { diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp index fe6bbc61..308aecd6 100644 --- a/wsd/DocumentBroker.cpp +++ b/wsd/DocumentBroker.cpp @@ -595,11 +595,21 @@ bool DocumentBroker::saveToStorage(const std::string& sessionId, return res; } +bool DocumentBroker::saveAsToStorage(const std::string& sessionId, const std::string& saveAsPath, const std::string& saveAsFilename) +{ + assertCorrectThread(); + + return saveToStorageInternal(sessionId, true, "", saveAsPath, saveAsFilename); +} + bool DocumentBroker::saveToStorageInternal(const std::string& sessionId, - bool success, const std::string& result) + bool success, const std::string& result, + const std::string& saveAsPath, const std::string& saveAsFilename) { assertCorrectThread(); + const bool isSaveAs = !saveAsPath.empty(); + // If save requested, but core didn't save because document was unmodified // notify the waiting thread, if any. LOG_TRC("Saving to storage docKey [" << _docKey << "] for session [" << sessionId << @@ -620,12 +630,12 @@ bool DocumentBroker::saveToStorageInternal(const std::string& sessionId, } const Authorization auth = it->second->getAuthorization(); - const auto uri = it->second->getPublicUri().toString(); + const auto uri = isSaveAs? saveAsPath: it->second->getPublicUri().toString(); // If we aren't destroying the last editable session just yet, // and the file timestamp hasn't changed, skip saving. const auto newFileModifiedTime = Poco::File(_storage->getRootFilePath()).getLastModified(); - if (!_lastEditableSession && newFileModifiedTime == _lastFileModifiedTime) + if (!isSaveAs && !_lastEditableSession && newFileModifiedTime == _lastFileModifiedTime) { // Nothing to do. LOG_DBG("Skipping unnecessary saving to URI [" << uri << "] with docKey [" << _docKey << @@ -641,24 +651,32 @@ bool DocumentBroker::saveToStorageInternal(const std::string& sessionId, // storage behind our backs. assert(_storage && _tileCache); - StorageBase::SaveResult storageSaveResult = _storage->saveLocalFileToStorage(auth); + StorageBase::SaveResult storageSaveResult = _storage->saveLocalFileToStorage(auth, saveAsPath, saveAsFilename); if (storageSaveResult == StorageBase::SaveResult::OK) { - setModified(false); - _lastFileModifiedTime = newFileModifiedTime; - _tileCache->saveLastModified(_lastFileModifiedTime); - _lastSaveTime = std::chrono::steady_clock::now(); - _poll->wakeup(); + if (!isSaveAs) + { + setModified(false); + _lastFileModifiedTime = newFileModifiedTime; + _tileCache->saveLastModified(_lastFileModifiedTime); + _lastSaveTime = std::chrono::steady_clock::now(); + _poll->wakeup(); - // So set _documentLastModifiedTime then - _documentLastModifiedTime = _storage->getFileInfo()._modifiedTime; + // So set _documentLastModifiedTime then + _documentLastModifiedTime = _storage->getFileInfo()._modifiedTime; - // After a successful save, we are sure that document in the storage is same as ours - _documentChangedInStorage = false; + // After a successful save, we are sure that document in the storage is same as ours + _documentChangedInStorage = false; - Log::debug() << "Saved docKey [" << _docKey << "] to URI [" << uri - << "] and updated tile cache. Document modified timestamp: " - << _documentLastModifiedTime << Log::end; + Log::debug() << "Saved docKey [" << _docKey << "] to URI [" << uri + << "] and updated tile cache. Document modified timestamp: " + << _documentLastModifiedTime << Log::end; + } + else + { + Log::debug() << "Saved As docKey [" << _docKey << "] to URI [" << uri + << "] successfully."; + } return true; } else if (storageSaveResult == StorageBase::SaveResult::DISKFULL) diff --git a/wsd/DocumentBroker.hpp b/wsd/DocumentBroker.hpp index 0bc29996..2b76fbd1 100644 --- a/wsd/DocumentBroker.hpp +++ b/wsd/DocumentBroker.hpp @@ -241,6 +241,11 @@ public: /// Save the document to Storage if it needs persisting. bool saveToStorage(const std::string& sesionId, bool success, const std::string& result = "", bool force = false); + + /// 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 isModified() const { return _isModified; } void setModified(const bool value); /// Save the document if the document is modified. @@ -355,7 +360,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 = ""); + bool saveToStorageInternal(const std::string& sesionId, bool success, const std::string& result = "", const std::string& saveAsPath = std::string(), const std::string& saveAsFilename = std::string()); /// Loads a new session and adds to the sessions container. size_t addSessionInternal(const std::shared_ptr<ClientSession>& session); diff --git a/wsd/Storage.cpp b/wsd/Storage.cpp index 14482b0c..c36c07ca 100644 --- a/wsd/Storage.cpp +++ b/wsd/Storage.cpp @@ -297,7 +297,7 @@ std::string LocalStorage::loadStorageFileToLocal(const Authorization& /*auth*/) #endif } -StorageBase::SaveResult LocalStorage::saveLocalFileToStorage(const Authorization& /*auth*/) +StorageBase::SaveResult LocalStorage::saveLocalFileToStorage(const Authorization& /*auth*/, const std::string& /*saveAsPath*/, const std::string& /*saveAsFilename*/) { try { @@ -659,13 +659,15 @@ std::string WopiStorage::loadStorageFileToLocal(const Authorization& auth) return ""; } -StorageBase::SaveResult WopiStorage::saveLocalFileToStorage(const Authorization& auth) +StorageBase::SaveResult WopiStorage::saveLocalFileToStorage(const Authorization& auth, const std::string& saveAsPath, const std::string& saveAsFilename) { // TODO: Check if this URI has write permission (canWrite = true) const auto size = getFileSize(_jailedFilePath); + const bool isSaveAs = !saveAsPath.empty(); + Poco::URI uriObject(_uri); - uriObject.setPath(uriObject.getPath() + "/contents"); + uriObject.setPath(isSaveAs? uriObject.getPath(): uriObject.getPath() + "/contents"); auth.authorizeURI(uriObject); LOG_INF("Uploading URI via WOPI [" << uriObject.toString() << "] from [" << _jailedFilePath + "]."); @@ -677,30 +679,48 @@ StorageBase::SaveResult WopiStorage::saveLocalFileToStorage(const Authorization& std::unique_ptr<Poco::Net::HTTPClientSession> psession(getHTTPClientSession(uriObject)); Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_POST, uriObject.getPathAndQuery(), Poco::Net::HTTPMessage::HTTP_1_1); - request.set("X-WOPI-Override", "PUT"); + request.set("User-Agent", WOPI_AGENT_STRING); auth.authorizeRequest(request); - if (!_forceSave) + + if (!isSaveAs) + { + // normal save + request.set("X-WOPI-Override", "PUT"); + request.set("X-LOOL-WOPI-IsModifiedByUser", _isUserModified? "true": "false"); + request.set("X-LOOL-WOPI-IsAutosave", _isAutosave? "true": "false"); + + if (!_forceSave) + { + // Request WOPI host to not overwrite if timestamps mismatch + request.set("X-LOOL-WOPI-Timestamp", + Poco::DateTimeFormatter::format(Poco::DateTime(_fileInfo._modifiedTime), + Poco::DateTimeFormat::ISO8601_FRAC_FORMAT)); + } + } + else { - // Request WOPI host to not overwrite if timestamps mismatch - request.set("X-LOOL-WOPI-Timestamp", - Poco::DateTimeFormatter::format(Poco::DateTime(_fileInfo._modifiedTime), - Poco::DateTimeFormat::ISO8601_FRAC_FORMAT)); + // save as + request.set("X-WOPI-Override", "PUT_RELATIVE"); + request.set("X-WOPI-RelativeTarget", saveAsFilename); + request.set("X-WOPI-Size", std::to_string(size)); } - request.set("X-LOOL-WOPI-IsModifiedByUser", _isUserModified? "true": "false"); - request.set("X-LOOL-WOPI-IsAutosave", _isAutosave? "true": "false"); request.setContentType("application/octet-stream"); request.setContentLength(size); addStorageDebugCookie(request); std::ostream& os = psession->sendRequest(request); - std::ifstream ifs(_jailedFilePath); + + const std::string filePath(isSaveAs? saveAsPath: _jailedFilePath); + std::ifstream ifs(filePath); Poco::StreamCopier::copyStream(ifs, os); Poco::Net::HTTPResponse response; std::istream& rs = psession->receiveResponse(response); Poco::StreamCopier::copyStream(rs, oss); - LOG_INF("WOPI::PutFile response: " << oss.str()); - LOG_INF("WOPI::PutFile uploaded " << size << " bytes from [" << _jailedFilePath << + + std::string wopiLog(isSaveAs? "WOPI::PutFileRelative": "WOPI::PutFile"); + LOG_INF(wopiLog << " response: " << oss.str()); + LOG_INF(wopiLog << " uploaded " << size << " bytes from [" << filePath << "] -> [" << uriObject.toString() << "]: " << response.getStatus() << " " << response.getReason()); @@ -711,7 +731,7 @@ StorageBase::SaveResult WopiStorage::saveLocalFileToStorage(const Authorization& if (parseJSON(oss.str(), object)) { const std::string lastModifiedTime = getJSONValue<std::string>(object, "LastModifiedTime"); - LOG_TRC("WOPI::PutFile returns LastModifiedTime [" << lastModifiedTime << "]."); + LOG_TRC(wopiLog << " returns LastModifiedTime [" << lastModifiedTime << "]."); _fileInfo._modifiedTime = iso8601ToTimestamp(lastModifiedTime); // Reset the force save flag now, if any, since we are done saving @@ -720,7 +740,7 @@ StorageBase::SaveResult WopiStorage::saveLocalFileToStorage(const Authorization& } else { - LOG_WRN("Invalid/Missing JSON found in WOPI::PutFile response"); + LOG_WRN("Invalid/Missing JSON found in " << wopiLog << " response"); } } else if (response.getStatus() == Poco::Net::HTTPResponse::HTTP_REQUESTENTITYTOOLARGE) @@ -745,7 +765,7 @@ StorageBase::SaveResult WopiStorage::saveLocalFileToStorage(const Authorization& } else { - LOG_WRN("Invalid/missing JSON in WOPI::PutFile response"); + LOG_WRN("Invalid/missing JSON in " << wopiLog << " response"); } } } @@ -766,7 +786,7 @@ std::string WebDAVStorage::loadStorageFileToLocal(const Authorization& /*auth*/) return _uri.toString(); } -StorageBase::SaveResult WebDAVStorage::saveLocalFileToStorage(const Authorization& /*auth*/) +StorageBase::SaveResult WebDAVStorage::saveLocalFileToStorage(const Authorization& /*auth*/, const std::string& /*saveAsPath*/, const std::string& /*saveAsFilename*/) { // TODO: implement webdav PUT. return StorageBase::SaveResult::OK; diff --git a/wsd/Storage.hpp b/wsd/Storage.hpp index 57fc9ad4..d698ba45 100644 --- a/wsd/Storage.hpp +++ b/wsd/Storage.hpp @@ -112,7 +112,8 @@ public: virtual std::string loadStorageFileToLocal(const Authorization& auth) = 0; /// Writes the contents of the file back to the source. - virtual SaveResult saveLocalFileToStorage(const Authorization& auth) = 0; + /// @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; static size_t getFileSize(const std::string& filename); @@ -184,7 +185,7 @@ public: std::string loadStorageFileToLocal(const Authorization& auth) override; - SaveResult saveLocalFileToStorage(const Authorization& auth) override; + SaveResult saveLocalFileToStorage(const Authorization& auth, const std::string& saveAsPath, const std::string& saveAsFilename) override; private: /// True if the jailed file is not linked but copied. @@ -287,7 +288,7 @@ public: /// uri format: http://server/<...>/wopi*/files/<id>/content std::string loadStorageFileToLocal(const Authorization& auth) override; - SaveResult saveLocalFileToStorage(const Authorization& auth) override; + SaveResult saveLocalFileToStorage(const Authorization& auth, const std::string& saveAsPath, const std::string& saveAsFilename) override; /// Total time taken for making WOPI calls during load std::chrono::duration<double> getWopiLoadDuration() const { return _wopiLoadDuration; } @@ -317,7 +318,7 @@ public: std::string loadStorageFileToLocal(const Authorization& auth) override; - SaveResult saveLocalFileToStorage(const Authorization& auth) override; + SaveResult saveLocalFileToStorage(const Authorization& auth, const std::string& saveAsPath, const std::string& saveAsFilename) override; private: std::unique_ptr<AuthBase> _authAgent; _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits