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 <hcas...@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 <hcas...@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 <hcas...@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: */
_______________________________________________
Libreoffice-commits mailing list
libreoffice-comm...@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits

Reply via email to