[libreoffice-online] 01/04: Imported Upstream version 5.4.0.0.beta2
Rene Engelhard
rene at moszumanska.debian.org
Thu Jun 15 19:11:15 UTC 2017
This is an automated email from the git hooks/post-receive script.
rene pushed a commit to branch experimental
in repository libreoffice-online.
commit a7b23dd11ae2a1bce4b20695e96cf3917e9330da
Author: Rene Engelhard <rene at rene-engelhard.de>
Date: Tue Jun 13 09:54:07 2017 +0200
Imported Upstream version 5.4.0.0.beta2
---
Makefile.am | 9 --
Makefile.in | 7 +-
common/Session.cpp | 3 +-
configure | 20 ++--
configure.ac | 2 +-
dist_git_hash | 2 +-
kit/ChildSession.cpp | 20 +---
kit/ChildSession.hpp | 5 +-
kit/Kit.cpp | 37 ++++----
loleaflet/dist/errormessages.js | 6 +-
loleaflet/dist/toolbar/toolbar.js | 5 +-
loleaflet/main.js | 2 +-
loleaflet/po/help-de.po | 6 +-
loleaflet/po/ui-cs.po | 20 ++--
loleaflet/po/ui-da.po | 23 +++--
loleaflet/po/ui-eu.po | 8 +-
loleaflet/po/ui-pt.po | 8 +-
loleaflet/src/control/Control.Dialog.js | 5 +-
loleaflet/src/control/Control.Header.js | 11 ++-
loleaflet/src/core/Socket.js | 36 ++++++--
loleaflet/src/layer/AnnotationManager.js | 16 ++++
loleaflet/src/map/Map.js | 15 ++-
loleaflet/src/map/handler/Map.Keyboard.js | 3 +-
loleaflet/src/map/handler/Map.WOPI.js | 6 +-
loolwsd.spec | 6 +-
test/WhiteBoxTests.cpp | 2 +-
test/httpwstest.cpp | 53 +++++++++++
wsd/ClientSession.cpp | 5 +-
wsd/DocumentBroker.cpp | 147 ++++++++++++++++--------------
wsd/DocumentBroker.hpp | 8 +-
wsd/Exceptions.hpp | 8 ++
wsd/FileServer.cpp | 5 +-
wsd/LOOLWSD.cpp | 17 +++-
wsd/Storage.cpp | 92 ++++++++-----------
wsd/Storage.hpp | 53 +----------
wsd/reference.txt | 8 +-
36 files changed, 371 insertions(+), 308 deletions(-)
diff --git a/Makefile.am b/Makefile.am
index 73c2614..9cc4402 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -107,15 +107,6 @@ loolwsd_fuzzer_SOURCES = $(loolwsd_sources) \
$(shared_sources) \
kit/DummyLibreOfficeKit.cpp
-loolnb_SOURCES = net/loolnb.cpp \
- net/Socket.cpp \
- common/Log.cpp \
- common/SigUtil.cpp \
- common/Util.cpp
-if ENABLE_SSL
-loolnb_SOURCES += net/Ssl.cpp
-endif
-
clientnb_SOURCES = net/clientnb.cpp \
common/Log.cpp \
common/Util.cpp
diff --git a/Makefile.in b/Makefile.in
index 2c0722d..bf49f71 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -89,8 +89,7 @@ bin_PROGRAMS = loolwsd$(EXEEXT) loolforkit$(EXEEXT) loolmap$(EXEEXT) \
noinst_PROGRAMS = clientnb$(EXEEXT) connect$(EXEEXT) \
lokitclient$(EXEEXT) loolforkit-nocaps$(EXEEXT) \
loolwsd_fuzzer$(EXEEXT)
- at ENABLE_SSL_TRUE@am__append_4 = net/Ssl.cpp
- at ENABLE_SSL_TRUE@am__append_5 = net/Ssl.hpp \
+ at ENABLE_SSL_TRUE@am__append_4 = net/Ssl.hpp \
@ENABLE_SSL_TRUE@ net/SslSocket.hpp
subdir = .
@@ -639,8 +638,6 @@ loolwsd_fuzzer_SOURCES = $(loolwsd_sources) \
$(shared_sources) \
kit/DummyLibreOfficeKit.cpp
-loolnb_SOURCES = net/loolnb.cpp net/Socket.cpp common/Log.cpp \
- common/SigUtil.cpp common/Util.cpp $(am__append_4)
clientnb_SOURCES = net/clientnb.cpp \
common/Log.cpp \
common/Util.cpp
@@ -681,7 +678,7 @@ shared_headers = common/Common.hpp common/IoUtil.hpp \
common/Rectangle.hpp common/SigUtil.hpp common/security.h \
common/SpookyV2.h net/DelaySocket.hpp net/ServerSocket.hpp \
net/Socket.hpp net/WebSocketHandler.hpp tools/Replay.hpp \
- $(am__append_5)
+ $(am__append_4)
kit_headers = kit/ChildSession.hpp \
kit/DummyLibreOfficeKit.hpp \
kit/Kit.hpp \
diff --git a/common/Session.cpp b/common/Session.cpp
index d875668..d29613d 100644
--- a/common/Session.cpp
+++ b/common/Session.cpp
@@ -164,8 +164,7 @@ void Session::shutdown(const WebSocketHandler::StatusCodes statusCode, const std
static_cast<unsigned>(statusCode) << "] and reason [" << statusMessage << "].");
// See protocol.txt for this application-level close frame.
- const std::string msg = "close: " + statusMessage;
- sendTextFrame(msg.data(), msg.size());
+ sendMessage("close: " + statusMessage);
WebSocketHandler::shutdown(statusCode, statusMessage);
}
diff --git a/configure b/configure
index 7325efd..790a4d8 100755
--- a/configure
+++ b/configure
@@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for libreoffice-online 5.4.0.0.beta1.
+# Generated by GNU Autoconf 2.69 for libreoffice-online 5.4.0.0.beta2.
#
# Report bugs to <libreoffice at lists.freedesktop.org>.
#
@@ -590,8 +590,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='libreoffice-online'
PACKAGE_TARNAME='libreoffice-online'
-PACKAGE_VERSION='5.4.0.0.beta1'
-PACKAGE_STRING='libreoffice-online 5.4.0.0.beta1'
+PACKAGE_VERSION='5.4.0.0.beta2'
+PACKAGE_STRING='libreoffice-online 5.4.0.0.beta2'
PACKAGE_BUGREPORT='libreoffice at lists.freedesktop.org'
PACKAGE_URL=''
@@ -1376,7 +1376,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
-\`configure' configures libreoffice-online 5.4.0.0.beta1 to adapt to many kinds of systems.
+\`configure' configures libreoffice-online 5.4.0.0.beta2 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1447,7 +1447,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
- short | recursive ) echo "Configuration of libreoffice-online 5.4.0.0.beta1:";;
+ short | recursive ) echo "Configuration of libreoffice-online 5.4.0.0.beta2:";;
esac
cat <<\_ACEOF
@@ -1599,7 +1599,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
-libreoffice-online configure 5.4.0.0.beta1
+libreoffice-online configure 5.4.0.0.beta2
generated by GNU Autoconf 2.69
Copyright (C) 2012 Free Software Foundation, Inc.
@@ -2089,7 +2089,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
-It was created by libreoffice-online $as_me 5.4.0.0.beta1, which was
+It was created by libreoffice-online $as_me 5.4.0.0.beta2, which was
generated by GNU Autoconf 2.69. Invocation command line was
$ $0 $@
@@ -11466,7 +11466,7 @@ fi
# Define the identity of the package.
PACKAGE='libreoffice-online'
- VERSION='5.4.0.0.beta1'
+ VERSION='5.4.0.0.beta2'
cat >>confdefs.h <<_ACEOF
@@ -17571,7 +17571,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
-This file was extended by libreoffice-online $as_me 5.4.0.0.beta1, which was
+This file was extended by libreoffice-online $as_me 5.4.0.0.beta2, which was
generated by GNU Autoconf 2.69. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -17637,7 +17637,7 @@ _ACEOF
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
ac_cs_version="\\
-libreoffice-online config.status 5.4.0.0.beta1
+libreoffice-online config.status 5.4.0.0.beta2
configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\"
diff --git a/configure.ac b/configure.ac
index 876bb9b..446c1f0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3,7 +3,7 @@
AC_PREREQ([2.69])
-AC_INIT([libreoffice-online], [5.4.0.0.beta1], [libreoffice at lists.freedesktop.org])
+AC_INIT([libreoffice-online], [5.4.0.0.beta2], [libreoffice at lists.freedesktop.org])
LT_INIT([shared, disable-static, dlopen])
AM_INIT_AUTOMAKE([1.11 silent-rules subdir-objects tar-pax -Wno-portability])
diff --git a/dist_git_hash b/dist_git_hash
index 12d7ea7..93404fd 100644
--- a/dist_git_hash
+++ b/dist_git_hash
@@ -1 +1 @@
-3cb12a2
+b396188
diff --git a/kit/ChildSession.cpp b/kit/ChildSession.cpp
index 1fbc194..d68bcf5 100644
--- a/kit/ChildSession.cpp
+++ b/kit/ChildSession.cpp
@@ -97,19 +97,14 @@ bool ChildSession::_handleInput(const char *buffer, int length)
getLOKitDocument()->setView(_viewId);
- // Get the list of view ids from the core
- const int viewCount = getLOKitDocument()->getViewsCount();
- std::vector<int> viewIds(viewCount);
- getLOKitDocument()->getViewIds(viewIds.data(), viewCount);
-
int curPart = 0;
if (getLOKitDocument()->getDocumentType() != LOK_DOCTYPE_TEXT)
curPart = getLOKitDocument()->getPart();
- lockLokDoc.unlock();
-
// Notify all views about updated view info
- _docManager.notifyViewInfo(viewIds);
+ _docManager.notifyViewInfo();
+
+ lockLokDoc.unlock();
if (getLOKitDocument()->getDocumentType() != LOK_DOCTYPE_TEXT)
{
@@ -370,15 +365,8 @@ bool ChildSession::loadDocument(const char * /*buffer*/, int /*length*/, const s
return false;
}
- // Get the list of view ids from the core
- const int viewCount = getLOKitDocument()->getViewsCount();
- std::vector<int> viewIds(viewCount);
- getLOKitDocument()->getViewIds(viewIds.data(), viewCount);
-
- lockLokDoc.unlock();
-
// Inform everyone (including this one) about updated view info
- _docManager.notifyViewInfo(viewIds);
+ _docManager.notifyViewInfo();
LOG_INF("Loaded session " << getId());
return true;
diff --git a/kit/ChildSession.hpp b/kit/ChildSession.hpp
index b1fad05..703df77 100644
--- a/kit/ChildSession.hpp
+++ b/kit/ChildSession.hpp
@@ -45,8 +45,9 @@ public:
/// Access to the document instance.
virtual std::shared_ptr<lok::Document> getLOKitDocument() = 0;
- /// Send updated view info to all active sessions
- virtual void notifyViewInfo(const std::vector<int>& viewIds) = 0;
+ /// Send updated view info to all active sessions.
+ virtual void notifyViewInfo() = 0;
+
/// Get a view ID <-> UserInfo map.
virtual std::map<int, UserInfo> getViewInfo() = 0;
virtual std::mutex& getMutex() = 0;
diff --git a/kit/Kit.cpp b/kit/Kit.cpp
index 628e929..304a62e 100644
--- a/kit/Kit.cpp
+++ b/kit/Kit.cpp
@@ -1022,14 +1022,8 @@ private:
if (viewCount > 0)
{
- // Get the list of view ids from the core
- std::vector<int> viewIds(viewCount);
- _loKitDocument->getViewIds(viewIds.data(), viewCount);
-
- lockLokDoc.unlock();
-
// Broadcast updated view info
- notifyViewInfo(viewIds);
+ notifyViewInfo();
}
}
@@ -1051,11 +1045,18 @@ private:
}
/// Notify all views of viewId and their associated usernames
- void notifyViewInfo(const std::vector<int>& viewIds) override
+ void notifyViewInfo() override
{
- // Store the list of viewid, username mapping in a map
- std::map<int, UserInfo> viewInfoMap = getViewInfo();
- std::map<std::string, int> viewColorsMap = getViewColors();
+ Util::assertIsLocked(_documentMutex);
+
+ // Get the list of view ids from the core
+ const int viewCount = getLOKitDocument()->getViewsCount();
+ std::vector<int> viewIds(viewCount);
+ getLOKitDocument()->getViewIds(viewIds.data(), viewCount);
+
+ const std::map<int, UserInfo> viewInfoMap = _sessionUserInfo;
+
+ const std::map<std::string, int> viewColorsMap = getViewColors();
// Double check if list of viewids from core and our list matches,
// and create an array of JSON objects containing id and username
@@ -1101,17 +1102,13 @@ private:
// Get the color value for all author names from the core
std::map<std::string, int> getViewColors()
{
- std::string colorValues;
- std::map<std::string, int> viewColors;
-
- {
- std::unique_lock<std::mutex> lock(_documentMutex);
+ Util::assertIsLocked(_documentMutex);
- char* values = _loKitDocument->getCommandValues(".uno:TrackedChangeAuthors");
- colorValues = std::string(values == nullptr ? "" : values);
- std::free(values);
- }
+ char* values = _loKitDocument->getCommandValues(".uno:TrackedChangeAuthors");
+ const std::string colorValues = std::string(values == nullptr ? "" : values);
+ std::free(values);
+ std::map<std::string, int> viewColors;
try
{
if (!colorValues.empty())
diff --git a/loleaflet/dist/errormessages.js b/loleaflet/dist/errormessages.js
index d1eccac..8ea1185 100644
--- a/loleaflet/dist/errormessages.js
+++ b/loleaflet/dist/errormessages.js
@@ -10,6 +10,8 @@ exports.sessionexpired = _('Your session has been expired. Further changes to do
exports.faileddocloading = _('Failed to load the document. Please ensure the file type is supported and not corrupted, and try again.');
exports.storage = {
- savediskfull: _('Save failed due to no disk space left on storage server. Document will now be read-only. Please contact the server administrator to continue editing.'),
- savefailed: _('Document cannot be saved to storage. Check your permissions or contact the storage server administrator.')
+ 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 to storage server (%storageserver) due to expired or invalid access token. Refresh your session to not to lose your work.'),
+ savefailed: _('Document cannot be saved to storage. Check your permissions or contact the storage server (%storageserver) administrator.')
};
diff --git a/loleaflet/dist/toolbar/toolbar.js b/loleaflet/dist/toolbar/toolbar.js
index 55d5035..d98dbef 100644
--- a/loleaflet/dist/toolbar/toolbar.js
+++ b/loleaflet/dist/toolbar/toolbar.js
@@ -219,7 +219,7 @@ function onClick(id, item, subItem) {
}
}
else if (id === 'save') {
- map.save(true, true);
+ map.save(false /* An explicit save should terminate cell edit */, false /* An explicit save should save it again */);
}
else if (id === 'repair') {
map._socket.sendMessage('commandvalues command=.uno:DocumentRepair');
@@ -1331,6 +1331,7 @@ function updateCommandValues() {
if (typeof commandValues === 'undefined') {
return;
}
+ data = []; // reset data in order to avoid that the font select box is populated with styles, too.
// Old browsers like IE11 et al don't like Object.keys with
// empty arguments
if (typeof commandValues === 'object') {
@@ -1426,7 +1427,7 @@ map.on('commandresult', function (e) {
}
}
else if ((commandName === '.uno:Undo' || commandName === '.uno:Redo') &&
- e.success === true && e.result.value && e.result.value === '130') { /*UNDO_CONFLICT*/
+ e.success === true && e.result.value && !isNaN(e.result.value)) { /*UNDO_CONFLICT*/
$('#tb_toolbar-up_item_repair').w2overlay({ html: '<div style="padding: 10px; line-height: 150%">' +
_('Conflict Undo/Redo with multiple users. Please use document repair to resolve') + '</div>'});
}
diff --git a/loleaflet/main.js b/loleaflet/main.js
index fed5957..48200cd 100644
--- a/loleaflet/main.js
+++ b/loleaflet/main.js
@@ -113,7 +113,7 @@ var map = L.map('map', {
wopi: isWopi,
alwaysActive: alwaysActive,
idleTimeoutSecs: idleTimeoutSecs, // Dim when user is idle.
- outOfFocusTimeoutSecs: outOfFocusTimeoutSecs, // Dim after switching tabs.
+ outOfFocusTimeoutSecs: outOfFocusTimeoutSecs // Dim after switching tabs.
});
// toolbar.js (loaded in <script> tag accesses map as global variable,
// so expose it
diff --git a/loleaflet/po/help-de.po b/loleaflet/po/help-de.po
index 06d6437..6140af0 100644
--- a/loleaflet/po/help-de.po
+++ b/loleaflet/po/help-de.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-04-24 18:10+0000\n"
+"POT-Creation-Date: 2017-06-07 08:00+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
"Language-Team: LANGUAGE <LL at li.org>\n"
@@ -10,7 +10,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Generator: Translate Toolkit 2.0.0\n"
+"X-Generator: Translate Toolkit 2.1.0\n"
"X-Pootle-Path: /de/libo_online/loleaflet-help-de.po\n"
"X-Pootle-Revision: 829964\n"
@@ -775,7 +775,7 @@ msgstr "Strg + Alt + C"
#: dist%2Floleaflet-help.html+div.div.table.tr.td%3A123
msgid "Insert soft hyphen"
-msgstr "Weichen Trennstrich einfügen"
+msgstr "Weiches Trennzeichen einfügen"
#: dist%2Floleaflet-help.html+div.div.table.tr.td%3A123
msgid "Ctrl + -"
diff --git a/loleaflet/po/ui-cs.po b/loleaflet/po/ui-cs.po
index 19ecaea..2b77fca 100644
--- a/loleaflet/po/ui-cs.po
+++ b/loleaflet/po/ui-cs.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-05-22 08:00+0000\n"
+"POT-Creation-Date: 2017-06-07 08:00+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
"Language-Team: LANGUAGE <LL at li.org>\n"
@@ -10,9 +10,9 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Generator: Translate Toolkit 2.0.0\n"
+"X-Generator: Translate Toolkit 2.1.0\n"
"X-Pootle-Path: /cs/libo_online/loleaflet-ui-cs.po\n"
-"X-Pootle-Revision: 2522834\n"
+"X-Pootle-Revision: 2581025\n"
#: admin.strings.js:5
msgid "Admin console"
@@ -2004,12 +2004,11 @@ msgstr ""
#: src/control/Control.Menubar.js:35
msgid "Previous"
-msgstr ""
+msgstr "Předchozí"
#: src/control/Control.Menubar.js:36
-#, fuzzy
msgid "Next"
-msgstr "Text"
+msgstr "Další"
#: src/control/Control.Menubar.js:39
#: src/control/Control.Menubar.js:194
@@ -2024,9 +2023,8 @@ msgid "Full screen"
msgstr "Celá obrazovka"
#: src/control/Control.Menubar.js:46
-#, fuzzy
msgid "Formatting Marks"
-msgstr "Formátovací značka"
+msgstr "Řídicí znaky"
#: src/control/Control.Menubar.js:50
#: src/control/Control.Menubar.js:202
@@ -2577,7 +2575,7 @@ msgstr "Toto je nepříjemné, nemůžeme se připojit k dokumentu. Zkuste to zn
#: src/layer/marker/Annotation.js:156
msgid "Accept change"
-msgstr ""
+msgstr "Přijmout změnu"
#: src/layer/marker/Annotation.js:161
msgid "Reject change"
@@ -2589,11 +2587,11 @@ msgstr "Otevřít nabídku"
#: src/layer/tile/TileLayer.js:182
msgid "Modify"
-msgstr ""
+msgstr "Změnit"
#: src/layer/tile/TileLayer.js:194
msgid "Remove"
-msgstr ""
+msgstr "Odstranit"
#: src/map/Map.js:131
msgid "Initializing..."
diff --git a/loleaflet/po/ui-da.po b/loleaflet/po/ui-da.po
index fc5d30c..310b451 100644
--- a/loleaflet/po/ui-da.po
+++ b/loleaflet/po/ui-da.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-05-22 08:00+0000\n"
+"POT-Creation-Date: 2017-06-07 08:00+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
"Language-Team: LANGUAGE <LL at li.org>\n"
@@ -10,9 +10,9 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Generator: Translate Toolkit 2.0.0\n"
+"X-Generator: Translate Toolkit 2.1.0\n"
"X-Pootle-Path: /da/libo_online/loleaflet-ui-da.po\n"
-"X-Pootle-Revision: 2561692\n"
+"X-Pootle-Revision: 2604903\n"
#: admin.strings.js:5
msgid "Admin console"
@@ -724,7 +724,7 @@ msgstr "Dig"
#: dist/toolbar/toolbar.js:1604
msgid "Readonly"
-msgstr ""
+msgstr "Skrivebeskyttet"
#: src/admin/AdminSocketBase.js:46
msgid "Connection error"
@@ -1870,19 +1870,19 @@ msgstr "Optimal kolonnebredde"
#: src/control/Control.ContextMenu.js:135
msgid "Internal Cut"
-msgstr ""
+msgstr "Klip internt"
#: src/control/Control.ContextMenu.js:138
msgid "Internal Copy"
-msgstr ""
+msgstr "Kopier internt"
#: src/control/Control.ContextMenu.js:141
msgid "Internal Paste"
-msgstr ""
+msgstr "Indsæt internt"
#: src/control/Control.ContextMenu.js:163
msgid "Internal Paste Special"
-msgstr ""
+msgstr "Indsæt speciel internt"
#: src/control/Control.DocumentRepair.js:32
msgid "Repair Document"
@@ -1991,7 +1991,7 @@ msgstr "Vælg alle"
#: src/control/Control.Menubar.js:31
msgid "Track Changes"
-msgstr ""
+msgstr "Registrer ændringer"
#: src/control/Control.Menubar.js:32
msgid "Record"
@@ -2006,9 +2006,8 @@ msgid "Previous"
msgstr "Forrige"
#: src/control/Control.Menubar.js:36
-#, fuzzy
msgid "Next"
-msgstr "Tekst"
+msgstr "Næste"
#: src/control/Control.Menubar.js:39
#: src/control/Control.Menubar.js:194
@@ -2447,7 +2446,7 @@ msgstr "Er du sikker på, at du vil slette dette dias?"
#: src/control/Control.MetricInput.js:38
msgid "(100th/mm)"
-msgstr ""
+msgstr "(100th/mm)"
#: src/control/Control.MetricInput.js:44
msgid "Add: "
diff --git a/loleaflet/po/ui-eu.po b/loleaflet/po/ui-eu.po
index 7933a70..e40195a 100644
--- a/loleaflet/po/ui-eu.po
+++ b/loleaflet/po/ui-eu.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-05-22 08:01+0000\n"
+"POT-Creation-Date: 2017-06-07 08:00+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
"Language-Team: LANGUAGE <LL at li.org>\n"
@@ -10,9 +10,9 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Generator: Translate Toolkit 2.0.0\n"
+"X-Generator: Translate Toolkit 2.1.0\n"
"X-Pootle-Path: /eu/libo_online/loleaflet-ui-eu.po\n"
-"X-Pootle-Revision: 2534367\n"
+"X-Pootle-Revision: 2608213\n"
#: admin.strings.js:5
msgid "Admin console"
@@ -2446,7 +2446,7 @@ msgstr "Ziur zaude diapositiba hau ezabatu nahi duzula?"
#: src/control/Control.MetricInput.js:38
msgid "(100th/mm)"
-msgstr ""
+msgstr "(1/100 mm)"
#: src/control/Control.MetricInput.js:44
msgid "Add: "
diff --git a/loleaflet/po/ui-pt.po b/loleaflet/po/ui-pt.po
index f547f6a..296c277 100644
--- a/loleaflet/po/ui-pt.po
+++ b/loleaflet/po/ui-pt.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-04-24 08:02+0000\n"
+"POT-Creation-Date: 2017-06-07 12:29+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
"Language-Team: LANGUAGE <LL at li.org>\n"
@@ -10,9 +10,9 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Generator: Translate Toolkit 2.0.0\n"
+"X-Generator: Translate Toolkit 2.1.0\n"
"X-Pootle-Path: /pt/libo_online/loleaflet-ui-pt.po\n"
-"X-Pootle-Revision: 2509302\n"
+"X-Pootle-Revision: 2578810\n"
#: admin.strings.js:5
msgid "Admin console"
@@ -651,7 +651,7 @@ msgstr "Média"
#: dist/toolbar/toolbar.js:972
msgid "CountA"
-msgstr "ContarA"
+msgstr "Contar.VAL"
#: dist/toolbar/toolbar.js:973
msgid "Count"
diff --git a/loleaflet/src/control/Control.Dialog.js b/loleaflet/src/control/Control.Dialog.js
index 733c5cd..daad5e6 100644
--- a/loleaflet/src/control/Control.Dialog.js
+++ b/loleaflet/src/control/Control.Dialog.js
@@ -21,7 +21,10 @@ L.Control.Dialog = L.Control.extend({
if (e.msg) {
vex.dialog.alert(e.msg);
}
- else if (e.cmd && e.kind) {
+ else if (e.cmd == 'load' && e.kind == 'docunloading') {
+ // Handled by transparently retrying.
+ return;
+ } else if (e.cmd && e.kind) {
var msg = 'The server encountered a \'' + e.kind + '\' error while' +
' parsing the \'' + e.cmd + '\' command.';
vex.dialog.alert(msg);
diff --git a/loleaflet/src/control/Control.Header.js b/loleaflet/src/control/Control.Header.js
index 13bcd94..7b30933 100644
--- a/loleaflet/src/control/Control.Header.js
+++ b/loleaflet/src/control/Control.Header.js
@@ -33,11 +33,8 @@ L.Control.Header = L.Control.extend({
if (this._selection.start === -1 && this._selection.end === -1)
return;
var childs = element.children;
- // if the selection is cleared when the end selection cell is not in the current viewport,
- // we have _selection.end === -1, since only a portion of the header is fetched;
- // so, without the following hack, the selection would not be cleared correctly
var start = (this._selection.start === -1) ? 0 : this._selection.start;
- var end = (this._selection.end === -1) ? childs.length : this._selection.end + 1;
+ var end = this._selection.end + 1;
for (var iterator = start; iterator < end; iterator++) {
this.unselect(childs[iterator]);
}
@@ -72,6 +69,12 @@ L.Control.Header = L.Control.extend({
}
}
+ // if end is greater than the last fetched header position set itEnd to the max possible value
+ // without this hack selecting a whole row and then a whole column (or viceversa) leads to an incorrect selection
+ if (itStart !== -1 && itEnd === -1) {
+ itEnd = childs.length - 1;
+ }
+
// we need to unselect the row (column) header entry for the current cell cursor position
// since the selection could be due to selecting a whole row (column), so the selection
// does not start by clicking on a cell
diff --git a/loleaflet/src/core/Socket.js b/loleaflet/src/core/Socket.js
index 0364390..fdce2a5 100644
--- a/loleaflet/src/core/Socket.js
+++ b/loleaflet/src/core/Socket.js
@@ -111,6 +111,7 @@ L.Socket = L.Class.extend({
_onSocketOpen: function () {
console.debug('_onSocketOpen:');
+ this._map._serverRecycling = false;
// Always send the protocol version number.
// TODO: Move the version number somewhere sensible.
this._doSend('loolclient ' + this.ProtocolVersionNumber);
@@ -228,6 +229,9 @@ L.Socket = L.Class.extend({
if (textMsg === 'ownertermination') {
msg = _('Session terminated by document owner');
}
+ else if (textMsg === 'idle') {
+ msg = _('Session terminated due to idleness');
+ }
else if (textMsg === 'shuttingdown') {
msg = _('Server is shutting down for maintenance (auto-saving)');
}
@@ -235,6 +239,7 @@ L.Socket = L.Class.extend({
msg = _('Server is recycling and will be available shortly');
this._map._active = false;
+ this._map._serverRecycling = true;
// Prevent reconnecting the world at the same time.
var min = 5000;
@@ -301,24 +306,38 @@ L.Socket = L.Class.extend({
this._map.fire('postMessage', {msgId: 'Session_Closed'});
}
- if (textMsg === 'idle') {
- this._map._active = false;
- }
-
- if (textMsg === 'ownertermination') {
+ if (textMsg === 'idle' || textMsg === 'ownertermination') {
this._map.remove();
}
return;
}
else if (textMsg.startsWith('error:') && command.errorCmd === 'storage') {
+ var storageError;
if (command.errorKind === 'savediskfull') {
- this._map.fire('error', {msg: errorMessages.storage.savediskfull});
+ storageError = errorMessages.storage.savediskfull;
}
else if (command.errorKind === 'savefailed') {
- // Just warn the user
- this._map.fire('warn', {msg: errorMessages.storage.savefailed});
+ storageError = errorMessages.storage.savefailed;
+ }
+ else if (command.errorKind === 'saveunauthorized') {
+ storageError = errorMessages.storage.saveunauthorized;
}
+ else if (command.errorKind === 'loadfailed') {
+ storageError = errorMessages.storage.loadfailed;
+ // Since this is a document load failure, wsd will disconnect the socket anyway,
+ // better we do it first so that another error message doesn't override this one
+ // upon socket close.
+ this._map.hideBusy();
+ this.close();
+ }
+
+ // Parse the storage url as link
+ var tmpLink = document.createElement('a');
+ tmpLink.href = this._map.options.doc;
+ // Insert the storage server address to be more friendly
+ storageError = storageError.replace('%storageserver', tmpLink.host);
+ this._map.fire('warn', {msg: storageError});
return;
}
@@ -328,6 +347,7 @@ L.Socket = L.Class.extend({
this._map.fire('error', {msg: errorMessages.diskfull});
}
else if (command.errorKind === 'unauthorized') {
+ this._map.hideBusy();
this._map.fire('error', {msg: errorMessages.unauthorized});
}
diff --git a/loleaflet/src/layer/AnnotationManager.js b/loleaflet/src/layer/AnnotationManager.js
index 3ef0dc8..7dc2e5c 100644
--- a/loleaflet/src/layer/AnnotationManager.js
+++ b/loleaflet/src/layer/AnnotationManager.js
@@ -118,12 +118,20 @@ L.AnnotationManager = L.Class.extend({
fill: function (comments) {
var comment;
this.clear();
+ // items contains redlines
+ var ordered = !this._items.length > 0;
for (var index in comments) {
comment = comments[index];
this.adjustComment(comment);
this._items.push(L.annotation(this._map.options.maxBounds.getSouthEast(), comment).addTo(this._map));
}
if (this._items.length > 0) {
+ if (!ordered) {
+ this._items.sort(function(a, b) {
+ return Math.abs(a._data.anchorPos.min.y) - Math.abs(b._data.anchorPos.min.y) ||
+ Math.abs(a._data.anchorPos.min.x) - Math.abs(b._data.anchorPos.min.x);
+ });
+ }
this._map._docLayer._updateMaxBounds(true);
this.layout();
}
@@ -132,6 +140,8 @@ L.AnnotationManager = L.Class.extend({
fillChanges: function(redlines) {
var changecomment;
this.clearChanges();
+ // items contains comments
+ var ordered = !this._items.length > 0;
for (var idx in redlines) {
changecomment = redlines[idx];
if (!this.adjustRedLine(changecomment)) {
@@ -141,6 +151,12 @@ L.AnnotationManager = L.Class.extend({
this._items.push(L.annotation(this._map.options.maxBounds.getSouthEast(), changecomment).addTo(this._map));
}
if (this._items.length > 0) {
+ if (!ordered) {
+ this._items.sort(function(a, b) {
+ return Math.abs(a._data.anchorPos.min.y) - Math.abs(b._data.anchorPos.min.y) ||
+ Math.abs(a._data.anchorPos.min.x) - Math.abs(b._data.anchorPos.min.x);
+ });
+ }
this._map._docLayer._updateMaxBounds(true);
this.layout();
}
diff --git a/loleaflet/src/map/Map.js b/loleaflet/src/map/Map.js
index 0e8d631..c13a208 100644
--- a/loleaflet/src/map/Map.js
+++ b/loleaflet/src/map/Map.js
@@ -71,6 +71,7 @@ L.Map = L.Evented.extend({
this._fatal = false;
this._enabled = true;
this._debugAlwaysActive = false; // disables the dimming / document inactivity when true
+ this._serverRecycling = false;
vex.dialogID = -1;
@@ -159,7 +160,7 @@ L.Map = L.Evented.extend({
addView: function(viewInfo) {
this._viewInfo[viewInfo.id] = viewInfo;
- this.fire('postMessage', {msgId: 'View_Added', args: {ViewId: viewInfo.id, UserId: viewInfo.userid, UserName: viewInfo.username, Color: viewInfo.color, ReadOnly: viewInfo.readonly}});
+ this.fire('postMessage', {msgId: 'View_Added', args: {ViewId: viewInfo.id, UserId: viewInfo.userid, UserName: viewInfo.username, Color: L.LOUtil.rgbToHex(viewInfo.color), ReadOnly: viewInfo.readonly}});
// Fire last, otherwise not all events are handled correctly.
this.fire('addview', {viewId: viewInfo.id, username: viewInfo.username, readonly: this.isViewReadOnly(viewInfo.id)});
@@ -777,6 +778,10 @@ L.Map = L.Evented.extend({
},
_activate: function () {
+ if (this._serverRecycling) {
+ return;
+ }
+
console.debug('_activate:');
clearTimeout(vex.timer);
@@ -867,6 +872,10 @@ L.Map = L.Evented.extend({
},
_startInactiveTimer: function () {
+ if (this._serverRecycling) {
+ return;
+ }
+
console.debug('_startInactiveTimer:');
clearTimeout(vex.timer);
var map = this;
@@ -876,6 +885,10 @@ L.Map = L.Evented.extend({
},
_deactivate: function () {
+ if (this._serverRecycling) {
+ return;
+ }
+
console.debug('_deactivate:');
clearTimeout(vex.timer);
diff --git a/loleaflet/src/map/handler/Map.Keyboard.js b/loleaflet/src/map/handler/Map.Keyboard.js
index 812ea84..1109b35 100644
--- a/loleaflet/src/map/handler/Map.Keyboard.js
+++ b/loleaflet/src/map/handler/Map.Keyboard.js
@@ -511,7 +511,8 @@ L.Map.Keyboard = L.Handler.extend({
this._map.print();
return true;
case 83: // s
- this._map.save(true, true);
+ this._map.save(false /* An explicit save should terminate cell edit */,
+ false /* An explicit save should save it again */);
return true;
case 86: // v
case 118: // v (Safari)
diff --git a/loleaflet/src/map/handler/Map.WOPI.js b/loleaflet/src/map/handler/Map.WOPI.js
index 110c0ad..20d11a6 100644
--- a/loleaflet/src/map/handler/Map.WOPI.js
+++ b/loleaflet/src/map/handler/Map.WOPI.js
@@ -4,8 +4,10 @@
/* global title */
L.Map.WOPI = L.Handler.extend({
-
- PostMessageOrigin: false,
+ // If the CheckFileInfo call fails on server side, we won't have any PostMessageOrigin.
+ // So use '*' because we still needs to send 'close' message to the parent frame which
+ // wouldn't be possible otherwise.
+ PostMessageOrigin: '*',
DocumentLoadedTime: false,
HidePrintOption: false,
HideSaveOption: false,
diff --git a/loolwsd.spec b/loolwsd.spec
index af5d0c8..97fdbcf 100644
--- a/loolwsd.spec
+++ b/loolwsd.spec
@@ -11,12 +11,12 @@ Name: loolwsd%{name_suffix}
%else
Name: loolwsd
%endif
-Version: 5.4.0.0.beta1
+Version: 5.4.0.0.beta2
Release: 1%{?dist}
Vendor: %{vendor}
Summary: LibreOffice Online WebSocket Daemon
License: MPL
-Source0: loolwsd-5.4.0.0.beta1.tar.gz
+Source0: loolwsd-5.4.0.0.beta2.tar.gz
BuildRequires: libcap-devel libpng-devel poco-devel >= 1.7.5
%if 0%{?fedora} || 0%{?rhel} >= 7
BuildRequires: libpcap
@@ -58,7 +58,7 @@ Obsoletes: loleaflet <= 1.5.8
%debug_package
%endif
%prep
-%setup -n loolwsd-5.4.0.0.beta1
+%setup -n loolwsd-5.4.0.0.beta2
%build
%configure \
diff --git a/test/WhiteBoxTests.cpp b/test/WhiteBoxTests.cpp
index de4e79a..3493bed 100644
--- a/test/WhiteBoxTests.cpp
+++ b/test/WhiteBoxTests.cpp
@@ -347,7 +347,7 @@ public:
return nullptr;
}
- void notifyViewInfo(const std::vector<int>& /*viewIds*/) override
+ void notifyViewInfo() override
{
}
diff --git a/test/httpwstest.cpp b/test/httpwstest.cpp
index 4382158..3df7856 100644
--- a/test/httpwstest.cpp
+++ b/test/httpwstest.cpp
@@ -107,6 +107,7 @@ class HTTPWSTest : public CPPUNIT_NS::TestFixture
CPPUNIT_TEST(testCursorPosition);
CPPUNIT_TEST(testAlertAllUsers);
CPPUNIT_TEST(testViewInfoMsg);
+ CPPUNIT_TEST(testUndoConflict);
CPPUNIT_TEST_SUITE_END();
@@ -163,6 +164,7 @@ class HTTPWSTest : public CPPUNIT_NS::TestFixture
void testCursorPosition();
void testAlertAllUsers();
void testViewInfoMsg();
+ void testUndoConflict();
void loadDoc(const std::string& documentURL, const std::string& testname);
@@ -1149,6 +1151,8 @@ void HTTPWSTest::testInsertDelete()
std::cerr << "Deleting 10 slides." << std::endl;
for (size_t it = 1; it <= 10; it++)
{
+ // Explicitly delete the nth slide.
+ sendTextFrame(socket, "setclientpart part=" + std::to_string(it));
sendTextFrame(socket, "uno .uno:DeletePage");
response = getResponseString(socket, "status:");
CPPUNIT_ASSERT_MESSAGE("did not receive a status: message as expected", !response.empty());
@@ -2599,6 +2603,55 @@ void HTTPWSTest::testViewInfoMsg()
}
}
+void HTTPWSTest::testUndoConflict()
+{
+ const std::string testname = "testUndoConflict-";
+ Poco::JSON::Parser parser;
+ std::string docPath;
+ std::string docURL;
+ int conflict;
+
+ getDocumentPathAndURL("empty.odt", docPath, docURL, testname);
+
+ Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, docURL);
+ auto socket0 = connectLOKit(_uri, request, _response);
+ auto socket1 = connectLOKit(_uri, request, _response);
+
+ std::string response;
+ try
+ {
+ // Load first view
+ sendTextFrame(socket0, "load url=" + docURL);
+ response = getResponseString(socket0, "invalidatecursor:", testname + "0 ");
+
+ // Load second view
+ sendTextFrame(socket1, "load url=" + docURL);
+ response = getResponseString(socket1, "invalidatecursor:", testname + "1 ");
+
+ // edit first view
+ sendTextFrame(socket0, "key type=input char=97 key=0", testname);
+ response = getResponseString(socket0, "invalidatecursor:", testname + "0 ");
+ // edit second view
+ sendTextFrame(socket1, "key type=input char=98 key=0", testname);
+ response = getResponseString(socket1, "invalidatecursor:", testname + "1 ");
+ // try to undo first view
+ sendTextFrame(socket0, "uno .uno:Undo", testname);
+ // undo conflict
+ response = getResponseString(socket0, "unocommandresult:", testname + "0 ");
+ auto objJSON = parser.parse(response.substr(17)).extract<Poco::JSON::Object::Ptr>();
+ Poco::DynamicStruct dsJSON = *objJSON;
+ CPPUNIT_ASSERT_EQUAL(dsJSON["commandName"].toString(), std::string(".uno:Undo"));
+ CPPUNIT_ASSERT_EQUAL(dsJSON["success"].toString(), std::string("true"));
+ CPPUNIT_ASSERT_EQUAL(dsJSON["result"]["type"].toString(), std::string("long"));
+ CPPUNIT_ASSERT(Poco::strToInt(dsJSON["result"]["value"].toString(), conflict, 10));
+ CPPUNIT_ASSERT(conflict > 0); /*UNDO_CONFLICT*/
+ }
+ catch(const Poco::Exception& exc)
+ {
+ CPPUNIT_FAIL(exc.displayText());
+ }
+}
+
CPPUNIT_TEST_SUITE_REGISTRATION(HTTPWSTest);
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp
index 479bc2b..1f454a6 100644
--- a/wsd/ClientSession.cpp
+++ b/wsd/ClientSession.cpp
@@ -419,12 +419,12 @@ bool ClientSession::filterMessage(const std::string& message) const
LOG_WRN("No value of id in downloadas message");
}
}
- else if (tokens[0] == "gettextselection" || tokens[0] == "paste" || tokens[0] == "insertfile")
+ else if (tokens[0] == "gettextselection")
{
if (_wopiFileInfo && _wopiFileInfo->_disableCopy)
{
allowed = false;
- LOG_WRN("WOPI host has disabled copying to/from the document");
+ LOG_WRN("WOPI host has disabled copying from the document");
}
}
else if (isReadOnly())
@@ -737,6 +737,7 @@ bool ClientSession::forwardToClient(const std::shared_ptr<Message>& payload)
std::string ClientSession::getAccessToken() const
{
+ std::string accessToken;
Poco::URI::QueryParameters queryParams = _uriPublic.getQueryParameters();
for (auto& param: queryParams)
{
diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp
index 66a5de8..ea40f44 100644
--- a/wsd/DocumentBroker.cpp
+++ b/wsd/DocumentBroker.cpp
@@ -102,7 +102,16 @@ Poco::URI DocumentBroker::sanitizeURI(const std::string& uri)
std::string DocumentBroker::getDocKey(const Poco::URI& uri)
{
- return StorageBase::getUniqueDocId(uri);
+ // If multiple host-names are used to access us, then
+ // they must be aliases. Permission to access aliased hosts
+ // is checked at the point of accepting incoming connections.
+ // At this point storing the hostname artificially discriminates
+ // between aliases and forces same document (when opened from
+ // alias hosts) to load as separate documents and sharing doesn't
+ // work. Worse, saving overwrites one another.
+ std::string docKey;
+ Poco::URI::encode(uri.getPath(), "", docKey);
+ return docKey;
}
/// The Document Broker Poll - one of these in a thread per document
@@ -215,6 +224,7 @@ void DocumentBroker::pollThread()
static const bool AutoSaveEnabled = !std::getenv("LOOL_NO_AUTOSAVE");
static const size_t IdleDocTimeoutSecs = LOOLWSD::getConfigValue<int>(
"per_document.idle_timeout_secs", 3600);
+ std::string closeReason = "stopped";
// Main polling loop goodness.
while (!_stop && _poll->continuePolling() && !TerminationFlag)
@@ -232,30 +242,8 @@ void DocumentBroker::pollThread()
if (ShutdownRequestFlag)
{
- // Shutting down the server: notify clients, save, and stop.
- static const std::string msg("close: recycling");
-
- // First copy into local container, since removeSession
- // will erase from _sessions, but will leave the last.
- std::map<std::string, std::shared_ptr<ClientSession>> sessions = _sessions;
- for (const auto& pair : sessions)
- {
- std::shared_ptr<ClientSession> session = pair.second;
- try
- {
- // Notify the client and disconnect.
- session->sendMessage(msg);
- session->shutdown(WebSocketHandler::StatusCodes::ENDPOINT_GOING_AWAY, "recycling");
-
- // Remove session, save, and mark to destroy.
- removeSession(session->getId(), true);
- }
- catch (const std::exception& exc)
- {
- LOG_WRN("Error while shutting down client [" <<
- session->getName() << "]: " << exc.what());
- }
- }
+ closeReason = "recycling";
+ shutdownClients(closeReason);
}
else if (AutoSaveEnabled && !_stop &&
std::chrono::duration_cast<std::chrono::seconds>(now - last30SecCheckTime).count() >= 30)
@@ -273,6 +261,7 @@ void DocumentBroker::pollThread()
{
LOG_INF("Terminating " << (idle ? "idle" : "dead") <<
" DocumentBroker for docKey [" << getDocKey() << "].");
+ closeReason = (idle ? "idle" : "dead");
_stop = true;
}
}
@@ -281,11 +270,7 @@ void DocumentBroker::pollThread()
_poll->continuePolling() << ", ShutdownRequestFlag: " << ShutdownRequestFlag <<
", TerminationFlag: " << TerminationFlag << ".");
- // Terminate properly while we can.
- //TODO: pass some sensible reason.
- terminateChild("", false);
-
- // Flush socket data.
+ // Flush socket data first.
const int flushTimeoutMs = POLL_TIMEOUT_MS * 2; // ~1000ms
const auto flushStartTime = std::chrono::steady_clock::now();
while (_poll->getSocketCount())
@@ -298,6 +283,9 @@ void DocumentBroker::pollThread()
_poll->poll(std::min(flushTimeoutMs - elapsedMs, POLL_TIMEOUT_MS / 5));
}
+ // Terminate properly while we can.
+ terminateChild(closeReason, false);
+
// Stop to mark it done and cleanup.
_poll->stop();
_poll->removeSockets();
@@ -401,7 +389,6 @@ bool DocumentBroker::load(const std::shared_ptr<ClientSession>& session, const s
LOG_ERR("Failed to create Storage instance for [" << _docKey << "] in " << jailPath.toString());
return false;
}
-
firstInstance = true;
}
@@ -416,14 +403,6 @@ bool DocumentBroker::load(const std::shared_ptr<ClientSession>& session, const s
std::unique_ptr<WopiStorage::WOPIFileInfo> wopifileinfo = wopiStorage->getWOPIFileInfo(session->getAccessToken());
userid = wopifileinfo->_userid;
username = wopifileinfo->_username;
- if (firstInstance)
- {
- _hostInstanceId = wopifileinfo->_hostInstanceId;
- }
- else if (!_hostInstanceId.empty() && _hostInstanceId != wopifileinfo->_hostInstanceId)
- {
- throw UnauthorizedRequestException("Unauthorized WOPI host.");
- }
if (!wopifileinfo->_userCanWrite)
{
@@ -453,7 +432,10 @@ bool DocumentBroker::load(const std::shared_ptr<ClientSession>& session, const s
std::ostringstream ossWopiInfo;
wopiInfo->stringify(ossWopiInfo);
- session->sendTextFrame("wopi: " + ossWopiInfo.str());
+ // Contains PostMessageOrigin property which is necessary to post messages to parent
+ // frame. Important to send this message immediately and not enqueue it so that in case
+ // document load fails, loleaflet is able to tell its parent frame via PostMessage API.
+ session->sendMessage("wopi: " + ossWopiInfo.str());
// Mark the session as 'Document owner' if WOPI hosts supports it
if (userid == _storage->getFileInfo()._ownerId)
@@ -661,6 +643,11 @@ bool DocumentBroker::saveToStorageInternal(const std::string& sessionId,
sessionIt.second->sendTextFrame("error: cmd=storage kind=savediskfull");
}
}
+ else if (storageSaveResult == StorageBase::SaveResult::UNAUTHORIZED)
+ {
+ LOG_ERR("Cannot save docKey [" << _docKey << "] to storage URI [" << uri << "]. Invalid or expired access token. Notifying client.");
+ it->second->sendTextFrame("error: cmd=storage kind=saveunauthorized");
+ }
else if (storageSaveResult == StorageBase::SaveResult::FAILED)
{
//TODO: Should we notify all clients?
@@ -802,6 +789,25 @@ std::string DocumentBroker::getJailRoot() const
size_t DocumentBroker::addSession(const std::shared_ptr<ClientSession>& session)
{
+ try
+ {
+ return addSessionInternal(session);
+ }
+ catch (const std::exception& exc)
+ {
+ LOG_ERR("Failed to add session to [" << _docKey << "] with URI [" << session->getPublicUri().toString() << "]: " << exc.what());
+ if (_sessions.empty())
+ {
+ LOG_INF("Doc [" << _docKey << "] has no more sessions. Marking to destroy.");
+ _markToDestroy = true;
+ }
+
+ throw;
+ }
+}
+
+size_t DocumentBroker::addSessionInternal(const std::shared_ptr<ClientSession>& session)
+{
assertCorrectThread();
try
@@ -832,12 +838,7 @@ size_t DocumentBroker::addSession(const std::shared_ptr<ClientSession>& session)
_markToDestroy = false;
_stop = false;
- // Add and attach the session.
- _sessions.emplace(session->getId(), session);
- session->setAttached();
-
const auto id = session->getId();
- const auto count = _sessions.size();
// Request a new session from the child kit.
const std::string aMessage = "session " + id + ' ' + _docKey + ' ' + _docId;
@@ -846,6 +847,11 @@ size_t DocumentBroker::addSession(const std::shared_ptr<ClientSession>& session)
// Tell the admin console about this new doc
Admin::instance().addDoc(_docKey, getPid(), getFilename(), id, session->getUserName());
+ // Add and attach the session.
+ _sessions.emplace(session->getId(), session);
+ session->setAttached();
+
+ const auto count = _sessions.size();
LOG_TRC("Added " << (session->isReadOnly() ? "readonly" : "non-readonly") <<
" session [" << id << "] to docKey [" <<
_docKey << "] to have " << count << " sessions.");
@@ -1315,6 +1321,33 @@ bool DocumentBroker::forwardToClient(const std::shared_ptr<Message>& payload)
return false;
}
+void DocumentBroker::shutdownClients(const std::string& closeReason)
+{
+ assertCorrectThread();
+ LOG_INF("Terminating " << _sessions.size() << " clients of doc [" << _docKey << "].");
+
+ // First copy into local container, since removeSession
+ // will erase from _sessions, but will leave the last.
+ std::map<std::string, std::shared_ptr<ClientSession>> sessions = _sessions;
+ for (const auto& pair : sessions)
+ {
+ std::shared_ptr<ClientSession> session = pair.second;
+ try
+ {
+ // Notify the client and disconnect.
+ session->shutdown(WebSocketHandler::StatusCodes::ENDPOINT_GOING_AWAY, closeReason);
+
+ // Remove session, save, and mark to destroy.
+ removeSession(session->getId(), true);
+ }
+ catch (const std::exception& exc)
+ {
+ LOG_WRN("Error while shutting down client [" <<
+ session->getName() << "]: " << exc.what());
+ }
+ }
+}
+
void DocumentBroker::childSocketTerminated()
{
assertCorrectThread();
@@ -1326,17 +1359,7 @@ void DocumentBroker::childSocketTerminated()
// We could restore the kit if this was unexpected.
// For now, close the connections to cleanup.
- for (auto& pair : _sessions)
- {
- try
- {
- pair.second->shutdown(WebSocketHandler::StatusCodes::ENDPOINT_GOING_AWAY, "");
- }
- catch (const std::exception& ex)
- {
- LOG_ERR("Error while terminating client connection [" << pair.first << "]: " << ex.what());
- }
- }
+ shutdownClients("terminated");
}
void DocumentBroker::terminateChild(const std::string& closeReason, const bool rude)
@@ -1348,17 +1371,7 @@ void DocumentBroker::terminateChild(const std::string& closeReason, const bool r
// Close all running sessions
if (!rude)
{
- for (const auto& pair : _sessions)
- {
- try
- {
- pair.second->shutdown(WebSocketHandler::StatusCodes::ENDPOINT_GOING_AWAY, closeReason);
- }
- catch (const std::exception& ex)
- {
- LOG_ERR("Error while terminating client connection [" << pair.first << "]: " << ex.what());
- }
- }
+ shutdownClients(closeReason);
}
if (_childProcess)
diff --git a/wsd/DocumentBroker.hpp b/wsd/DocumentBroker.hpp
index 8ac67ee..23b699e 100644
--- a/wsd/DocumentBroker.hpp
+++ b/wsd/DocumentBroker.hpp
@@ -339,6 +339,10 @@ public:
bool sendUnoSave(const std::string& sessionId, bool dontTerminateEdit = true, bool dontSaveIfUnmodified = true);
private:
+
+ /// Shutdown all client connections with the given reason.
+ void shutdownClients(const std::string& closeReason);
+
/// This gracefully terminates the connection
/// with the child and cleans up ChildProcess etc.
void terminateChild(const std::string& closeReason, const bool rude);
@@ -346,6 +350,9 @@ private:
/// Saves the doc to the storage.
bool saveToStorageInternal(const std::string& sesionId, bool success, const std::string& result = "");
+ /// Loads a new session and adds to the sessions container.
+ size_t addSessionInternal(const std::shared_ptr<ClientSession>& session);
+
/// Removes a session by ID. Returns the new number of sessions.
size_t removeSessionInternal(const std::string& id);
@@ -369,7 +376,6 @@ private:
Poco::URI _uriJailed;
std::string _jailId;
std::string _filename;
- std::string _hostInstanceId;
/// The last time we tried saving, regardless of whether the
/// document was modified and saved or not.
diff --git a/wsd/Exceptions.hpp b/wsd/Exceptions.hpp
index 2c4905a..122c618 100644
--- a/wsd/Exceptions.hpp
+++ b/wsd/Exceptions.hpp
@@ -34,6 +34,14 @@ public:
using LoolException::LoolException;
};
+/// General exception thrown when we are not able to
+/// connect to storage.
+class StorageConnectionException : public LoolException
+{
+public:
+ using LoolException::LoolException;
+};
+
/// A bad-request exception that is meant to signify,
/// and translate into, an HTTP bad request.
class BadRequestException : public LoolException
diff --git a/wsd/FileServer.cpp b/wsd/FileServer.cpp
index 60b6a71..a50be21 100644
--- a/wsd/FileServer.cpp
+++ b/wsd/FileServer.cpp
@@ -207,7 +207,7 @@ void FileServerRequestHandler::handleRequest(const HTTPRequest& request, Poco::M
bool gzip = request.hasToken("Accept-Encoding", "gzip");
const std::string *content;
-#ifdef ENABLE_DEBUG
+#if ENABLE_DEBUG
if (std::getenv("LOOL_SERVE_FROM_FS"))
{
// Useful to not serve from memory sometimes especially during loleaflet development
@@ -507,7 +507,8 @@ void FileServerRequestHandler::preprocessFile(const HTTPRequest& request, Poco::
Poco::URI uriFrameAncestor(frameAncestor);
if (!frameAncestor.empty() && !uriFrameAncestor.getScheme().empty() && !uriFrameAncestor.getHost().empty())
{
- frameAncestor = uriFrameAncestor.getScheme() + "://" + uriFrameAncestor.getHost();
+ frameAncestor = uriFrameAncestor.getScheme() + "://" + uriFrameAncestor.getHost() + ":" + std::to_string(uriFrameAncestor.getPort());
+
LOG_TRC("Final frame ancestor: " << frameAncestor);
// Replaced by frame-ancestors in CSP but some oldies don't know about that
diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp
index 1c6c91d..7c188f1 100644
--- a/wsd/LOOLWSD.cpp
+++ b/wsd/LOOLWSD.cpp
@@ -1883,7 +1883,7 @@ private:
docBroker->addCallback([docBroker, moveSocket, clientSession, format]()
{
- auto streamSocket = std::static_pointer_cast<StreamSocket>(moveSocket);
+ auto streamSocket = std::static_pointer_cast<StreamSocket>(moveSocket);
clientSession->setSaveAsSocket(streamSocket);
// Move the socket into DocBroker.
@@ -2122,12 +2122,21 @@ private:
// Add and load the session.
docBroker->addSession(clientSession);
}
- catch (const std::exception& exc)
+ catch (const UnauthorizedRequestException& exc)
{
- LOG_ERR("Error while handling loading : " << exc.what());
+ LOG_ERR("Unauthorized Request while loading session for " << docBroker->getDocKey() << ": " << exc.what());
const std::string msg = "error: cmd=internal kind=unauthorized";
clientSession->sendMessage(msg);
- docBroker->stop();
+ }
+ catch (const StorageConnectionException& exc)
+ {
+ // Alert user about failed load
+ const std::string msg = "error: cmd=storage kind=loadfailed";
+ clientSession->sendMessage(msg);
+ }
+ catch (const std::exception& exc)
+ {
+ LOG_ERR("Error while loading : " << exc.what());
}
});
});
diff --git a/wsd/Storage.cpp b/wsd/Storage.cpp
index 0c31e53..54aa2d4 100644
--- a/wsd/Storage.cpp
+++ b/wsd/Storage.cpp
@@ -125,7 +125,7 @@ void StorageBase::initialize()
#endif
}
-bool StorageBase::isLocalhost(const std::string& targetHost)
+bool isLocalhost(const std::string& targetHost)
{
std::string targetAddress;
try
@@ -202,7 +202,7 @@ std::unique_ptr<StorageBase> StorageBase::create(const Poco::URI& uri, const std
{
LOG_INF("Public URI [" << uri.toString() << "] considered WOPI.");
const auto& targetHost = uri.getHost();
- if (isWopiHostAuthorized(targetHost))
+ if (WopiHosts.match(targetHost) || isLocalhost(targetHost))
{
return std::unique_ptr<StorageBase>(new WopiStorage(uri, jailRoot, jailPath));
}
@@ -213,39 +213,6 @@ std::unique_ptr<StorageBase> StorageBase::create(const Poco::URI& uri, const std
throw BadRequestException("No Storage configured or invalid URI.");
}
-std::string StorageBase::getUniqueDocId(const Poco::URI& uri)
-{
- std::string docId;
- if (uri.isRelative() || uri.getScheme() == "file")
- {
- Poco::URI::encode(uri.getPath(), "", docId);
- }
- else if (WopiEnabled)
- {
- const auto& targetHost = uri.getHost();
- if (isWopiHostAuthorized(targetHost))
- {
- std::string accessToken;
- Poco::URI::QueryParameters queryParams = uri.getQueryParameters();
- for (auto& param: queryParams)
- {
- if (param.first == "access_token")
- accessToken = param.second;
- }
-
- const std::unique_ptr<WopiStorage::WOPIFileInfo> info = WopiStorage::getWOPIFileInfo(uri, accessToken);
- const std::string prefix = !info->_hostInstanceId.empty()
- ? info->_hostInstanceId
- : (uri.getHost() + ':' + std::to_string(uri.getPort()));
- Poco::URI::encode(prefix + uri.getPath(), "", docId);
- }
- else
- throw UnauthorizedRequestException("No acceptable WOPI hosts found matching the target host [" + targetHost + "] in config.");
- }
-
- return docId;
-}
-
std::atomic<unsigned> LocalStorage::LastLocalStorageId;
std::unique_ptr<LocalStorage::LocalFileInfo> LocalStorage::getLocalFileInfo()
@@ -459,9 +426,10 @@ void addStorageDebugCookie(Poco::Net::HTTPRequest& request)
} // anonymous namespace
-std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(Poco::URI uriObject, const std::string& accessToken)
+std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(const std::string& accessToken)
{
// update the access_token to the one matching to the session
+ Poco::URI uriObject(_uri);
setQueryParameter(uriObject, "access_token", accessToken);
LOG_DBG("Getting info for wopi uri [" << uriObject.toString() << "].");
@@ -494,11 +462,17 @@ std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(Poco::UR
LOG_END(logger);
}
+ if (response.getStatus() != Poco::Net::HTTPResponse::HTTP_OK)
+ {
+ LOG_ERR("WOPI::CheckFileInfo failed with " << response.getStatus() << ' ' << response.getReason());
+ throw StorageConnectionException("WOPI::CheckFileInfo failed");
+ }
+
Poco::StreamCopier::copyToString(rs, resMsg);
}
catch(const Poco::Exception& pexc)
{
- LOG_ERR("Cannot get file info from WOPI storage uri [" + uriObject.toString() + "]. Error: " << pexc.displayText() <<
+ LOG_ERR("Cannot get file info from WOPI storage uri [" << uriObject.toString() << "]. Error: " << pexc.displayText() <<
(pexc.nested() ? " (" + pexc.nested()->displayText() + ")" : ""));
throw;
}
@@ -509,7 +483,6 @@ std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(Poco::UR
std::string ownerId;
std::string userId;
std::string userName;
- std::string hostInstanceId;
bool canWrite = false;
bool enableOwnerTermination = false;
std::string postMessageOrigin;
@@ -544,12 +517,11 @@ std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(Poco::UR
getWOPIValue(object, "DisableExport", disableExport);
getWOPIValue(object, "DisableCopy", disableCopy);
getWOPIValue(object, "LastModifiedTime", lastModifiedTime);
- getWOPIValue(object, "HostInstanceId", hostInstanceId);
}
else
{
LOG_ERR("WOPI::CheckFileInfo failed and no JSON payload returned. Access denied.");
- throw UnauthorizedRequestException("Access denied.");
+ throw UnauthorizedRequestException("Access denied. WOPI::CheckFileInfo failed on: " + uriObject.toString());
}
Poco::Timestamp modifiedTime = Poco::Timestamp::fromEpochTime(0);
@@ -574,7 +546,9 @@ std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(Poco::UR
}
}
- return std::unique_ptr<WopiStorage::WOPIFileInfo>(new WOPIFileInfo(filename, ownerId, modifiedTime, size, userId, userName, hostInstanceId, canWrite, postMessageOrigin, hidePrintOption, hideSaveOption, hideExportOption, enableOwnerTermination, disablePrint, disableExport, disableCopy, callDuration));
+ _fileInfo = FileInfo({filename, ownerId, modifiedTime, size});
+
+ return std::unique_ptr<WopiStorage::WOPIFileInfo>(new WOPIFileInfo({userId, userName, canWrite, postMessageOrigin, hidePrintOption, hideSaveOption, hideExportOption, enableOwnerTermination, disablePrint, disableExport, disableCopy, callDuration}));
}
/// uri format: http://server/<...>/wopi*/files/<id>/content
@@ -614,15 +588,25 @@ std::string WopiStorage::loadStorageFileToLocal(const std::string& accessToken)
LOG_END(logger);
}
- _jailedFilePath = Poco::Path(getLocalRootPath(), _fileInfo._filename).toString();
- std::ofstream ofs(_jailedFilePath);
- std::copy(std::istreambuf_iterator<char>(rs),
- std::istreambuf_iterator<char>(),
- std::ostreambuf_iterator<char>(ofs));
-
- LOG_INF("WOPI::GetFile downloaded " << getFileSize(_jailedFilePath) << " bytes from [" << uriObject.toString() <<
- "] -> " << _jailedFilePath << " in " << diff.count() << "s : " <<
- response.getStatus() << " " << response.getReason());
+ if (response.getStatus() != Poco::Net::HTTPResponse::HTTP_OK)
+ {
+ LOG_ERR("WOPI::GetFile failed with " << response.getStatus() << ' ' << response.getReason());
+ throw StorageConnectionException("WOPI::GetFile failed");
+ }
+ else // Successful
+ {
+ _jailedFilePath = Poco::Path(getLocalRootPath(), _fileInfo._filename).toString();
+ std::ofstream ofs(_jailedFilePath);
+ std::copy(std::istreambuf_iterator<char>(rs),
+ std::istreambuf_iterator<char>(),
+ std::ostreambuf_iterator<char>(ofs));
+ LOG_INF("WOPI::GetFile downloaded " << getFileSize(_jailedFilePath) << " bytes from [" << uriObject.toString() <<
+ "] -> " << _jailedFilePath << " in " << diff.count() << "s");
+
+ _isLoaded = true;
+ // Now return the jailed path.
+ return Poco::Path(_jailPath, _fileInfo._filename).toString();
+ }
}
catch(const Poco::Exception& pexc)
{
@@ -631,9 +615,7 @@ std::string WopiStorage::loadStorageFileToLocal(const std::string& accessToken)
throw;
}
- _isLoaded = true;
- // Now return the jailed path.
- return Poco::Path(_jailPath, _fileInfo._filename).toString();
+ return "";
}
StorageBase::SaveResult WopiStorage::saveLocalFileToStorage(const std::string& accessToken)
@@ -678,6 +660,10 @@ StorageBase::SaveResult WopiStorage::saveLocalFileToStorage(const std::string& a
{
saveResult = StorageBase::SaveResult::DISKFULL;
}
+ else if (response.getStatus() == Poco::Net::HTTPResponse::HTTP_UNAUTHORIZED)
+ {
+ saveResult = StorageBase::SaveResult::UNAUTHORIZED;
+ }
}
catch(const Poco::Exception& pexc)
{
diff --git a/wsd/Storage.hpp b/wsd/Storage.hpp
index 7cb7743..d0dff59 100644
--- a/wsd/Storage.hpp
+++ b/wsd/Storage.hpp
@@ -57,6 +57,7 @@ public:
{
OK,
DISKFULL,
+ UNAUTHORIZED,
FAILED
};
@@ -100,29 +101,11 @@ public:
static std::unique_ptr<StorageBase> create(const Poco::URI& uri,
const std::string& jailRoot,
const std::string& jailPath);
-
- /// Given the URI of a doc, return a unique doc ID.
- /// Wopi host aliases are resolved to unique host ID.
- static std::string getUniqueDocId(const Poco::URI& uri);
-
protected:
/// Returns the root path of the jail directory of docs.
std::string getLocalRootPath() const;
- /// Returns true iff WOPI is enabled, and the host is whitelisted (or local).
- static bool isWopiHostAuthorized(const std::string& host)
- {
- if (WopiEnabled)
- {
- return (WopiHosts.match(host) || isLocalhost(host));
- }
-
- return false;
- }
-
- static bool isLocalhost(const std::string& host);
-
protected:
const Poco::URI _uri;
std::string _localStorePath;
@@ -194,16 +177,11 @@ public:
"], jailPath: [" << jailPath << "], uri: [" << uri.toString() << "].");
}
- class WOPIFileInfo : public FileInfo
+ class WOPIFileInfo
{
public:
- WOPIFileInfo(const std::string& filename,
- const std::string& ownerId,
- const Poco::Timestamp& modifiedTime,
- size_t size,
- const std::string& userid,
+ WOPIFileInfo(const std::string& userid,
const std::string& username,
- const std::string& hostInstanceId,
const bool userCanWrite,
const std::string& postMessageOrigin,
const bool hidePrintOption,
@@ -214,10 +192,8 @@ public:
const bool disableExport,
const bool disableCopy,
const std::chrono::duration<double> callDuration)
- : FileInfo(filename, ownerId, modifiedTime, size),
- _userid(userid),
+ : _userid(userid),
_username(username),
- _hostInstanceId(hostInstanceId),
_userCanWrite(userCanWrite),
_postMessageOrigin(postMessageOrigin),
_hidePrintOption(hidePrintOption),
@@ -235,8 +211,6 @@ public:
std::string _userid;
/// Display Name of user accessing the file
std::string _username;
- /// Host instance ID (unique to the given host).
- std::string _hostInstanceId;
/// If user accessing the file has write permission
bool _userCanWrite;
/// WOPI Post message property
@@ -263,12 +237,7 @@ public:
/// provided during the initial creation of the WOPI storage.
/// Also extracts the basic file information from the response
/// which can then be obtained using getFileInfo()
- std::unique_ptr<WOPIFileInfo> getWOPIFileInfo(const std::string& accessToken)
- {
- std::unique_ptr<WOPIFileInfo> info = getWOPIFileInfo(_uri, accessToken);
- _fileInfo = FileInfo(info->_filename, info->_ownerId, info->_modifiedTime, info->_size);
- return info;
- }
+ std::unique_ptr<WOPIFileInfo> getWOPIFileInfo(const std::string& accessToken);
/// uri format: http://server/<...>/wopi*/files/<id>/content
std::string loadStorageFileToLocal(const std::string& accessToken) override;
@@ -278,11 +247,6 @@ public:
/// Total time taken for making WOPI calls during load
std::chrono::duration<double> getWopiLoadDuration() const { return _wopiLoadDuration; }
- /// Given the URI of a doc, return a unique doc ID.
- static std::string getUniqueDocId(const Poco::URI& uri);
-
- static std::unique_ptr<WOPIFileInfo> getWOPIFileInfo(Poco::URI uriObject, const std::string& accessToken);
-
private:
// Time spend in loading the file from storage
std::chrono::duration<double> _wopiLoadDuration;
@@ -310,13 +274,6 @@ public:
SaveResult saveLocalFileToStorage(const std::string& accessToken) override;
- /// Given the URI of a doc, return a unique doc ID.
- static std::string getUniqueDocId(const std::string& uri)
- {
- // TODO: Implement.
- return uri;
- }
-
private:
std::unique_ptr<AuthBase> _authAgent;
};
diff --git a/wsd/reference.txt b/wsd/reference.txt
index e1af6d4..5c70662 100644
--- a/wsd/reference.txt
+++ b/wsd/reference.txt
@@ -41,11 +41,9 @@ DisableExport
HideExportOption is assumed to be true
DisableCopy
- Disables copy/paste from/to the document in libreoffice online backend.
- However, it is still possible to do an "internal" cut/copy/paste i.e
- copy from the document and paste to the same document view. The context
- menu options pertaining to cut/copy/paste would be renamed to 'Internal
- Cut/Copy/Paste' when this option is mentioned.
+ Disables copying from the document in libreoffice online
+ backend. Pasting into the document would still be possible.
+ However, it is still possible to do an "internal" cut/copy/paste.
EnableOwnerTermination
If set to true, it allows the document owner (the one with OwnerId =
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-openoffice/libreoffice-online.git
More information about the Pkg-openoffice-commits
mailing list