summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJan Holesovsky <kendy@collabora.com>2017-10-20 18:12:05 +0200
committerJan Holesovsky <kendy@collabora.com>2017-10-26 11:11:38 +0200
commit7ff432a37010031afd3da4012882b160aba51ec9 (patch)
tree945d879f5a585db250eeecf44d773d8b3f0a0ed1
parenttdf#99744 SaveAs: Reverts parts of the previous Save As work. (diff)
downloadonline-7ff432a37010031afd3da4012882b160aba51ec9.tar.gz
online-7ff432a37010031afd3da4012882b160aba51ec9.zip
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
-rw-r--r--kit/ChildSession.cpp33
-rw-r--r--loleaflet/reference.html9
-rw-r--r--loleaflet/src/map/handler/Map.WOPI.js6
-rw-r--r--test/Makefile.am6
-rw-r--r--test/UnitOAuth.cpp5
-rw-r--r--test/UnitWOPI.cpp10
-rw-r--r--test/UnitWOPISaveAs.cpp72
-rw-r--r--test/WopiTestServer.hpp61
-rw-r--r--wsd/ClientSession.cpp50
-rw-r--r--wsd/DocumentBroker.cpp50
-rw-r--r--wsd/DocumentBroker.hpp7
-rw-r--r--wsd/Storage.cpp56
-rw-r--r--wsd/Storage.hpp9
13 files changed, 282 insertions, 92 deletions
diff --git a/kit/ChildSession.cpp b/kit/ChildSession.cpp
index c40a68822a..9c50638918 100644
--- a/kit/ChildSession.cpp
+++ b/kit/ChildSession.cpp
@@ -939,18 +939,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)
{
@@ -969,7 +988,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 8f1ea583ac..9a1f39aed6 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: &lt;String&gt;</nobr>
- <nobr>Path: &lt;String&gt;</nobr>
+ <nobr>Filename: &lt;String&gt;</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 26acea7b3c..7b2a44d430 100644
--- a/loleaflet/src/map/handler/Map.WOPI.js
+++ b/loleaflet/src/map/handler/Map.WOPI.js
@@ -223,11 +223,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 7ee4bcf2ba..aa54e0cb49 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -17,7 +17,7 @@ noinst_LTLIBRARIES = \
unit-storage.la unit-client.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)
@@ -80,6 +80,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
@@ -93,7 +95,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
+TESTS = unit-prefork.la unit-tilecache.la unit-timeout.la unit-oauth.la unit-wopi.la unit-wopi-saveas.la
# TESTS = unit-client.la
# TESTS += unit-admin.la
# TESTS += unit-storage.la
diff --git a/test/UnitOAuth.cpp b/test/UnitOAuth.cpp
index f88a4b555f..5b2b09c281 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 a8808d203c..338fb14580 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 0000000000..4bd3e9e065
--- /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 a3851b5d51..6324114d36 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 89d46b928f..63fa9eb9cc 100644
--- a/wsd/ClientSession.cpp
+++ b/wsd/ClientSession.cpp
@@ -654,38 +654,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 cd3da9fb10..1bac7182f1 100644
--- a/wsd/DocumentBroker.cpp
+++ b/wsd/DocumentBroker.cpp
@@ -621,11 +621,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 <<
@@ -646,12 +656,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 <<
@@ -664,24 +674,32 @@ bool DocumentBroker::saveToStorageInternal(const std::string& sessionId,
LOG_DBG("Persisting [" << _docKey << "] after saving to URI [" << uri << "].");
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 a7d27a43d8..0387de206a 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.
@@ -359,7 +364,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 433885f65a..6d0aa2c1c0 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 ef0e73adc8..60c921913e 100644
--- a/wsd/Storage.hpp
+++ b/wsd/Storage.hpp
@@ -114,7 +114,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);
@@ -186,7 +187,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.
@@ -289,7 +290,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; }
@@ -319,7 +320,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;