[Libreoffice-commits] online.git: Branch 'distro/collabora/collabora-online-1-0' - 3 commits - loleaflet/dist loleaflet/src loolwsd/LOOLWSD.cpp loolwsd/protocol.txt loolwsd/test loolwsd/UserMessages.hpp

Henry Castro hcastro at collabora.com
Tue Oct 4 00:01:55 UTC 2016


 loleaflet/dist/errormessages.js         |    1 
 loleaflet/src/control/Control.Dialog.js |    3 
 loleaflet/src/core/Socket.js            |   49 +++++----
 loleaflet/src/map/Map.js                |    8 -
 loolwsd/LOOLWSD.cpp                     |   50 +++++++++-
 loolwsd/UserMessages.hpp                |    7 +
 loolwsd/protocol.txt                    |    2 
 loolwsd/test/Makefile.am                |    2 
 loolwsd/test/data/empty.odt             |binary
 loolwsd/test/helpers.hpp                |   28 +++++
 loolwsd/test/httpwserror.cpp            |  158 ++++++++++++++++++++++++++++++++
 11 files changed, 277 insertions(+), 31 deletions(-)

New commits:
commit 21ab135ec4b96a948913c67d13e0f8d7f84895b7
Author: Henry Castro <hcastro at collabora.com>
Date:   Mon Oct 3 18:30:18 2016 -0400

    loleaflet: fix show dialog error

diff --git a/loleaflet/dist/errormessages.js b/loleaflet/dist/errormessages.js
index 6fa628b..0ea91ff 100644
--- a/loleaflet/dist/errormessages.js
+++ b/loleaflet/dist/errormessages.js
@@ -1,2 +1,3 @@
 var wrongwopisrc = _('Wrong WOPISrc, usage: WOPISrc=valid encoded URI, or file_path, usage: file_path=/path/to/doc/');
 var emptyhosturl = _('The host URL is empty. The loolwsd server is probably misconfigured, please contact the administrator.');
+var limitreached = _('This development build is limited to %0 documents, and %1 connections - to avoid the impression that it is suitable for deployment in large enterprises. To find out more about deploy    ing and scaling %2 checkout: <br/><a href=\"%3\">%3</a>.');
diff --git a/loleaflet/src/control/Control.Dialog.js b/loleaflet/src/control/Control.Dialog.js
index 9417a8b..41836ae 100644
--- a/loleaflet/src/control/Control.Dialog.js
+++ b/loleaflet/src/control/Control.Dialog.js
@@ -10,7 +10,8 @@ L.Control.Dialog = L.Control.extend({
 	},
 
 	_onError: function (e) {
-		if (vex.dialogID > 0) {
+		if (vex.dialogID > 0 && !this._map._fatal) {
+			// TODO. queue message errors and pop-up dialogs
 			// Close other dialogs before presenting a new one.
 			vex.close(vex.dialogID);
 		}
diff --git a/loleaflet/src/core/Socket.js b/loleaflet/src/core/Socket.js
index abf9165..59ceb10 100644
--- a/loleaflet/src/core/Socket.js
+++ b/loleaflet/src/core/Socket.js
@@ -10,16 +10,16 @@ L.Socket = L.Class.extend({
 		this._map = map;
 		try {
 			this.socket = new WebSocket(map.options.server + '/lool/' + encodeURIComponent(map.options.doc) + '/ws');
+			this.socket.onerror = L.bind(this._onSocketError, this);
+			this.socket.onclose = L.bind(this._onSocketClose, this);
+			this.socket.onopen = L.bind(this._onSocketOpen, this);
+			this.socket.onmessage = L.bind(this._onMessage, this);
+			this.socket.binaryType = 'arraybuffer';
 		} catch (e) {
-			this.fire('error', {msg: _('Oops, there is a problem connecting to LibreOffice Online : ' + e), cmd: 'socket', kind: 'failed', id: 3});
+			this._map.fire('error', {msg: _('Oops, there is a problem connecting to LibreOffice Online : ' + e), cmd: 'socket', kind: 'failed', id: 3});
 			return null;
 		}
 		this._msgQueue = [];
-		this.socket.onerror = L.bind(this._onSocketError, map);
-		this.socket.onclose = L.bind(this._onSocketClose, map);
-		this.socket.onopen = L.bind(this._onOpen, this);
-		this.socket.onmessage = L.bind(this._onMessage, this);
-		this.socket.binaryType = 'arraybuffer';
 	},
 
 	close: function () {
@@ -66,7 +66,7 @@ L.Socket = L.Class.extend({
 		this.socket.send(msg);
 	},
 
-	_onOpen: function () {
+	_onSocketOpen: function () {
 		// Always send the protocol version number.
 		// TODO: Move the version number somewhere sensible.
 		this._doSend('loolclient ' + this.ProtocolVersionNumber);
@@ -132,7 +132,7 @@ L.Socket = L.Class.extend({
 
 			// TODO: For now we expect perfect match in protocol versions
 			if (loolwsdVersionObj.Protocol !== this.ProtocolVersionNumber) {
-				this.fire('error', {msg: _('Unsupported server version.')});
+				this._map.fire('error', {msg: _('Unsupported server version.')});
 			}
 		}
 		else if (textMsg.startsWith('lokitversion ')) {
@@ -184,11 +184,20 @@ L.Socket = L.Class.extend({
 			}
 		}
 		else if (textMsg.startsWith('error:') && !this._map._docLayer) {
-			this.fail = true;
+			textMsg = textMsg.substring(6);
+			if (command.errorKind === 'limitreached') {
+				this._map._fatal = true;
+				textMsg = limitreached;
+				textMsg = textMsg.replace(/%0/g, command.params[0]);
+				textMsg = textMsg.replace(/%1/g, command.params[1]);
+				textMsg = textMsg.replace(/%2/g, (typeof brandProductName !== 'undefined' ? brandProductName : 'LibreOffice Online'));
+				textMsg = textMsg.replace(/%3/g, (typeof brandProductURL !== 'undefined' ? brandProductURL : 'https://wiki.documentfoundation.org/Development/LibreOffice_Online'));
+			}
+			this._map.fire('error', {msg: textMsg});
 		}
 		else if (textMsg.startsWith('statusindicator:')) {
 			//FIXME: We should get statusindicator when saving too, no?
-			this._map.showBusy('Connecting...', false);
+			this._map.showBusy(_('Connecting...'), false);
 		}
 		else if (!textMsg.startsWith('tile:') && !textMsg.startsWith('renderfont:')) {
 			// log the tile msg separately as we need the tile coordinates
@@ -291,23 +300,20 @@ L.Socket = L.Class.extend({
 	},
 
 	_onSocketError: function () {
-		this.hideBusy();
+		this._map.hideBusy();
 		// Let onclose (_onSocketClose) report errors.
 	},
 
-	_onSocketClose: function () {
-		this.hideBusy();
-		if (this._map) {
-			this._map._active = false;
-		}
+	_onSocketClose: function (e) {
+		this._map.hideBusy();
+		this._map._active = false;
 
-		if (this.fail) {
-			this.fire('error', {msg: _('Well, this is embarrassing, we cannot connect to your document. Please try again.'), cmd: 'socket', kind: 'closed', id: 4});
+		if (e.code && e.reason) {
+			this._map.fire('error', {msg: e.reason});
 		}
 		else {
-			this.fire('error', {msg: _('We are sorry, this is an unexpected connection error. Please try again.'), cmd: 'socket', kind: 'closed', id: 4});
+			this._map.fire('error', {msg: _('Well, this is embarrassing, we cannot connect to your document. Please try again.'), cmd: 'socket', kind: 'closed', id: 4});
 		}
-		this.fail = false;
 	},
 
 	parseServerCmd: function (msg) {
@@ -382,6 +388,9 @@ L.Socket = L.Class.extend({
 			else if (tokens[i].substring(0, 5) === 'font=') {
 				command.font = window.decodeURIComponent(tokens[i].substring(5));
 			}
+			else if (tokens[i].substring(0, 7) === 'params=') {
+				command.params = tokens[i].substring(7).split(',');
+			}
 		}
 		if (command.tileWidth && command.tileHeight && this._map._docLayer) {
 			var defaultZoom = this._map.options.zoom;
commit 8cd78ef851840e74e266a8e5f8c17c7cd645d171
Author: Henry Castro <hcastro at collabora.com>
Date:   Thu Sep 29 13:48:08 2016 -0400

    loleaflet: fix undefined variables

diff --git a/loleaflet/src/map/Map.js b/loleaflet/src/map/Map.js
index ea3f06f..3bcc9a2 100644
--- a/loleaflet/src/map/Map.js
+++ b/loleaflet/src/map/Map.js
@@ -736,7 +736,7 @@ L.Map = L.Evented.extend({
 			// A dialog is already dimming the screen and probably
 			// shows an error message. Leave it alone.
 			this._active = false;
-			this._docLayer._onMessage('textselection:', null);
+			this._docLayer && this._docLayer._onMessage('textselection:', null);
 			if (this._socket.connected()) {
 				this._socket.sendMessage('userinactive');
 			}
@@ -776,7 +776,7 @@ L.Map = L.Evented.extend({
 			$(options.appendLocation).append(options.$vex);
 			vex.setupBodyClassName(options.$vex);
 
-			map._docLayer._onMessage('textselection:', null);
+			map._doclayer && map._docLayer._onMessage('textselection:', null);
 			map._socket.sendMessage('userinactive');
 
 		}, 30 * 1000); // Dim in 30 seconds.
@@ -784,7 +784,7 @@ L.Map = L.Evented.extend({
 
 	_onLostFocus: function () {
 		var doclayer = this._docLayer;
-		if (doclayer._isCursorVisible && doclayer._isCursorOverlayVisible) {
+		if (doclayer && doclayer._isCursorVisible && doclayer._isCursorOverlayVisible) {
 			doclayer._visibleCursorOnLostFocus = doclayer._visibleCursor;
 			doclayer._isCursorOverlayVisibleOnLostFocus = doclayer._isCursorVisibleOnLostFocus = true;
 			doclayer._isCursorOverlayVisible = false;
@@ -796,7 +796,7 @@ L.Map = L.Evented.extend({
 
 	_onGotFocus: function () {
 		var doclayer = this._docLayer;
-		if (doclayer._isCursorVisibleOnLostFocus && doclayer._isCursorOverlayVisibleOnLostFocus) {
+		if (doclayer && doclayer._isCursorVisibleOnLostFocus && doclayer._isCursorOverlayVisibleOnLostFocus) {
 			// we restore the old cursor position by a small delay, so that if the user clicks
 			// inside the document we skip to restore it, so that the user does not see the cursor
 			// jumping from the old position to the new one
commit 41605abcd28ff4abaca81ad64ce1071c0420d08c
Author: Henry Castro <hcastro at collabora.com>
Date:   Mon Oct 3 16:57:59 2016 -0400

    loolwsd: bccu#2022, User warning on hitting limit

diff --git a/loolwsd/LOOLWSD.cpp b/loolwsd/LOOLWSD.cpp
index 00e9471..94ae192 100644
--- a/loolwsd/LOOLWSD.cpp
+++ b/loolwsd/LOOLWSD.cpp
@@ -174,6 +174,48 @@ static std::map<std::string, std::shared_ptr<MasterProcessSession>> AvailableChi
 static int careerSpanSeconds = 0;
 #endif
 
+namespace {
+
+static inline
+void lcl_shutdownLimitReached(WebSocket& ws)
+{
+    const std::string error = Poco::format(PAYLOAD_UNAVALABLE_LIMIT_REACHED, MAX_DOCUMENTS, MAX_CONNECTIONS);
+    const std::string close = Poco::format(SERVICE_UNAVALABLE_LIMIT_REACHED, static_cast<int>(WebSocket::WS_POLICY_VIOLATION));
+
+    /* loleaflet sends loolclient, load and partrectangles message immediately
+       after web socket handshake, so closing web socket fails loading page in
+       some sensible browsers. Ignore handshake messages and gracefully
+       close in order to send error messages.
+    */
+    try
+    {
+        int flags = 0;
+        int retries = 7;
+        std::vector<char> buffer(READ_BUFFER_SIZE * 100);
+
+        // 5 seconds timeout
+        ws.setReceiveTimeout(5000000);
+        do
+        {
+            // ignore loolclient, load and partpagerectangles
+            ws.receiveFrame(buffer.data(), buffer.capacity(), flags);
+            if (--retries == 4)
+            {
+                ws.sendFrame(error.data(), error.size());
+                ws.shutdown(WebSocket::WS_POLICY_VIOLATION, close);
+            }
+        }
+        while (retries > 0 && (flags & WebSocket::FRAME_OP_BITMASK) != WebSocket::FRAME_OP_CLOSE);
+    }
+    catch (Exception&)
+    {
+        ws.sendFrame(error.data(), error.size());
+        ws.shutdown(WebSocket::WS_POLICY_VIOLATION, close);
+    }
+}
+
+}
+
 static void forkChildren(const int number)
 {
     Util::assertIsLocked(newChildrenMutex);
@@ -620,7 +662,8 @@ private:
             {
                 --LOOLWSD::NumDocBrokers;
                 Log::error("Maximum number of open documents reached.");
-                throw WebSocketErrorMessageException(SERVICE_UNAVALABLE_LIMIT_REACHED);
+                lcl_shutdownLimitReached(*ws);
+                return;
             }
 #endif
 
@@ -841,7 +884,10 @@ public:
         {
             --LOOLWSD::NumConnections;
             Log::error("Maximum number of connections reached.");
-            throw WebSocketErrorMessageException(SERVICE_UNAVALABLE_LIMIT_REACHED);
+            // accept hand shake
+            WebSocket ws(request, response);
+            lcl_shutdownLimitReached(ws);
+            return;
         }
 #endif
 
diff --git a/loolwsd/UserMessages.hpp b/loolwsd/UserMessages.hpp
index 5208b09..36e6ad3 100644
--- a/loolwsd/UserMessages.hpp
+++ b/loolwsd/UserMessages.hpp
@@ -12,8 +12,11 @@
 #ifndef INCLUDED_USERMESSAGES_HPP
 #define INCLUDED_USERMESSAGES_HPP
 
-constexpr auto SERVICE_UNAVALABLE_INTERNAL_ERROR = "Service is unavailable. Please try again later and report to your administrator if the issue persists.";
-constexpr auto SERVICE_UNAVALABLE_LIMIT_REACHED = "This server has reached the number of connections or documents it supports at a given time.";
+//NOTE: For whatever reason Poco seems to trim the first character.
+
+constexpr auto SERVICE_UNAVALABLE_INTERNAL_ERROR = " Service is unavailable. Please try again later and report to your administrator if the issue persists.";
+constexpr auto SERVICE_UNAVALABLE_LIMIT_REACHED = "error: cmd=socket kind=close code=%d";
+constexpr auto PAYLOAD_UNAVALABLE_LIMIT_REACHED = "error: cmd=socket kind=limitreached params=%d,%d";
 
 #endif
 
diff --git a/loolwsd/protocol.txt b/loolwsd/protocol.txt
index 82819ea..a3b1c82 100644
--- a/loolwsd/protocol.txt
+++ b/loolwsd/protocol.txt
@@ -220,7 +220,7 @@ editlock: <1 or 0>
     Note that only one client can have the editlock at a time and
     others can only view.
 
-error: cmd=<command> kind=<kind> [code=<error_code>]
+error: cmd=<command> kind=<kind> [code=<error_code>] [params=1,2,3,...,N]
 <freeErrorText>
 
     <command> is the command part of the corresponding client->server
diff --git a/loolwsd/test/Makefile.am b/loolwsd/test/Makefile.am
index 9ce7c17..4f28533 100644
--- a/loolwsd/test/Makefile.am
+++ b/loolwsd/test/Makefile.am
@@ -28,7 +28,7 @@ wsd_sources = \
 
 test_CPPFLAGS = -DTDOC=\"$(top_srcdir)/test/data\" -I$(top_srcdir)
 test_SOURCES = TileCacheTests.cpp WhiteBoxTests.cpp integration-http-server.cpp \
-               httpwstest.cpp httpcrashtest.cpp test.cpp $(wsd_sources)
+               httpwstest.cpp httpcrashtest.cpp httpwserror.cpp test.cpp $(wsd_sources)
 test_LDADD = $(CPPUNIT_LIBS)
 
 # unit test modules:
diff --git a/loolwsd/test/data/empty.odt b/loolwsd/test/data/empty.odt
new file mode 100755
index 0000000..6b07475
Binary files /dev/null and b/loolwsd/test/data/empty.odt differ
diff --git a/loolwsd/test/helpers.hpp b/loolwsd/test/helpers.hpp
index e676ff1..7e570d3 100644
--- a/loolwsd/test/helpers.hpp
+++ b/loolwsd/test/helpers.hpp
@@ -15,6 +15,7 @@
 #include <thread>
 #include <regex>
 
+#include <Poco/BinaryReader.h>
 #include <Poco/DirectoryIterator.h>
 #include <Poco/Dynamic/Var.h>
 #include <Poco/FileStream.h>
@@ -156,6 +157,33 @@ std::string getTestServerURI()
 }
 
 inline
+int getErrorCode(Poco::Net::WebSocket& ws, std::string& message)
+{
+    int flags = 0;
+    int bytes = 0;
+    Poco::UInt16 statusCode = -1;
+    Poco::Buffer<char> buffer(READ_BUFFER_SIZE);
+
+    message.clear();
+    Poco::Timespan timeout(5000000);
+    ws.setReceiveTimeout(timeout);
+    do
+    {
+        bytes = ws.receiveFrame(buffer.begin(), READ_BUFFER_SIZE, flags);
+    }
+    while ((flags & Poco::Net::WebSocket::FRAME_OP_BITMASK) != Poco::Net::WebSocket::FRAME_OP_CLOSE);
+
+    if (bytes > 0)
+    {
+        Poco::MemoryBinaryReader reader(buffer, Poco::BinaryReader::NETWORK_BYTE_ORDER);
+        reader >> statusCode;
+        message.append(buffer.begin() + 2, bytes - 2);
+    }
+
+    return statusCode;
+}
+
+inline
 void getResponseMessage(Poco::Net::WebSocket& ws, const std::string& prefix, std::string& response, const bool isLine)
 {
     try
diff --git a/loolwsd/test/httpwserror.cpp b/loolwsd/test/httpwserror.cpp
new file mode 100644
index 0000000..8e7769d
--- /dev/null
+++ b/loolwsd/test/httpwserror.cpp
@@ -0,0 +1,158 @@
+/* -*- 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 <vector>
+#include <string>
+
+#include <Poco/Net/HTTPClientSession.h>
+#include <Poco/Net/HTTPRequest.h>
+#include <Poco/Net/HTTPResponse.h>
+#include <Poco/Net/HTTPSClientSession.h>
+#include <Poco/Net/NetException.h>
+#include <Poco/Net/WebSocket.h>
+#include <Poco/URI.h>
+
+#include <cppunit/extensions/HelperMacros.h>
+
+#include "Common.hpp"
+#include "LOOLProtocol.hpp"
+#include "helpers.hpp"
+
+using namespace helpers;
+
+class HTTPWSError : public CPPUNIT_NS::TestFixture
+{
+    const Poco::URI _uri;
+    Poco::Net::HTTPResponse _response;
+
+    CPPUNIT_TEST_SUITE(HTTPWSError);
+
+    CPPUNIT_TEST(testMaxDocuments);
+    CPPUNIT_TEST(testMaxConnections);
+
+    CPPUNIT_TEST_SUITE_END();
+
+    void testMaxDocuments();
+    void testMaxConnections();
+
+public:
+    HTTPWSError()
+        : _uri(helpers::getTestServerURI())
+    {
+#if ENABLE_SSL
+        Poco::Net::initializeSSL();
+        // Just accept the certificate anyway for testing purposes
+        Poco::SharedPtr<Poco::Net::InvalidCertificateHandler> invalidCertHandler = new Poco::Net::AcceptCertificateHandler(false);
+        Poco::Net::Context::Params sslParams;
+        Poco::Net::Context::Ptr sslContext = new Poco::Net::Context(Poco::Net::Context::CLIENT_USE, sslParams);
+        Poco::Net::SSLManager::instance().initializeClient(0, invalidCertHandler, sslContext);
+#endif
+    }
+
+#if ENABLE_SSL
+    ~HTTPWSError()
+    {
+        Poco::Net::uninitializeSSL();
+    }
+#endif
+
+    void setUp()
+    {
+    }
+
+    void tearDown()
+    {
+    }
+};
+
+void HTTPWSError::testMaxDocuments()
+{
+#if MAX_DOCUMENTS > 0
+    try
+    {
+        // Load a document.
+        std::string docPath;
+        std::string docURL;
+        std::string message;
+        Poco::UInt16 statusCode;
+        std::vector<std::shared_ptr<Poco::Net::WebSocket>> docs;
+
+        for(int it = 1; it <= MAX_DOCUMENTS; it++)
+        {
+            getDocumentPathAndURL("empty.odt", docPath, docURL);
+            Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, docURL);
+            docs.emplace_back(connectLOKit(_uri, request, _response));
+        }
+
+        // try to open MAX_DOCUMENTS + 1
+        getDocumentPathAndURL("empty.odt", docPath, docURL);
+        Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, docURL);
+        std::unique_ptr<Poco::Net::HTTPClientSession> session(helpers::createSession(_uri));
+        Poco::Net::WebSocket socket(*session, request, _response);
+        // send loolclient, load and partpagerectangles
+        sendTextFrame(socket, "loolclient ");
+        sendTextFrame(socket, "load ");
+        sendTextFrame(socket, "partpagerectangles ");
+        statusCode = getErrorCode(socket, message);
+        CPPUNIT_ASSERT_EQUAL(static_cast<Poco::UInt16>(Poco::Net::WebSocket::WS_POLICY_VIOLATION), statusCode);
+        CPPUNIT_ASSERT_MESSAGE("Wrong error message ", message.find("error: cmd=socket kind=close") != std::string::npos);
+    }
+    catch (const Poco::Exception& exc)
+    {
+        CPPUNIT_FAIL(exc.displayText());
+    }
+#endif
+}
+
+void HTTPWSError::testMaxConnections()
+{
+#if MAX_CONNECTIONS > 0
+    try
+    {
+        // Load a document.
+        std::string docPath;
+        std::string docURL;
+        std::string message;
+        Poco::UInt16 statusCode;
+        std::vector<std::shared_ptr<Poco::Net::WebSocket>> views;
+
+        getDocumentPathAndURL("empty.odt", docPath, docURL);
+        Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, docURL);
+        auto socket = loadDocAndGetSocket(_uri, docURL, "testMaxConnections ");
+
+        for(int it = 1; it < MAX_CONNECTIONS; it++)
+        {
+            std::unique_ptr<Poco::Net::HTTPClientSession> session(createSession(_uri));
+            auto ws = std::make_shared<Poco::Net::WebSocket>(*session, request, _response);
+            views.emplace_back(ws);
+        }
+
+        // try to connect MAX_CONNECTIONS + 1
+        std::unique_ptr<Poco::Net::HTTPClientSession> session(createSession(_uri));
+        auto socketN = std::make_shared<Poco::Net::WebSocket>(*session, request, _response);
+        // send loolclient, load and partpagerectangles
+        sendTextFrame(socketN, "loolclient ");
+        sendTextFrame(socketN, "load ");
+        sendTextFrame(socketN, "partpagerectangles ");
+        statusCode = getErrorCode(*socketN, message);
+        CPPUNIT_ASSERT_EQUAL(static_cast<Poco::UInt16>(Poco::Net::WebSocket::WS_POLICY_VIOLATION), statusCode);
+        CPPUNIT_ASSERT_MESSAGE("Wrong error message ", message.find("error: cmd=socket kind=close") != std::string::npos);
+    }
+    catch (const Poco::Exception& exc)
+    {
+        CPPUNIT_FAIL(exc.displayText());
+    }
+#endif
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(HTTPWSError);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */


More information about the Libreoffice-commits mailing list