common/Common.hpp | 8 common/Seccomp.cpp | 1 common/SigUtil.cpp | 1 configure.ac | 1 kit/ChildSession.hpp | 10 - kit/Kit.cpp | 334 +++++++++++++++++----------------- loleaflet/src/layer/tile/TileLayer.js | 88 ++++---- loolwsd.xml.in | 3 net/Socket.cpp | 173 +++++++++++++++++ net/Socket.hpp | 130 +++++++------ net/Ssl.cpp | 4 net/SslSocket.hpp | 16 + net/WebSocketHandler.hpp | 222 +++++++++++++++++----- test/Makefile.am | 8 test/TileCacheTests.cpp | 12 + test/WhiteBoxTests.cpp | 2 test/httpcrashtest.cpp | 1 test/httpwstest.cpp | 4 tools/WebSocketDump.cpp | 82 +++++++- wsd/Admin.cpp | 81 +++++++- wsd/Admin.hpp | 8 wsd/DocumentBroker.cpp | 5 wsd/DocumentBroker.hpp | 66 +++--- wsd/LOOLWSD.cpp | 117 ++--------- 24 files changed, 902 insertions(+), 475 deletions(-)
New commits: commit 27cc15027b59365fd4bf95c0c133541215c44771 Author: Ashod Nakashian <ashod.nakash...@collabora.co.uk> Date: Mon Jan 22 14:36:16 2018 -0500 ut: fix HTTPWSTest::testLoadTortureODP Change-Id: Ibe3bd98c1cd68da962cc8d93b837597fb7167f45 Reviewed-on: https://gerrit.libreoffice.org/48644 Reviewed-by: Ashod Nakashian <ashnak...@gmail.com> Tested-by: Ashod Nakashian <ashnak...@gmail.com> diff --git a/test/httpwstest.cpp b/test/httpwstest.cpp index ef1c8876a..bea861107 100644 --- a/test/httpwstest.cpp +++ b/test/httpwstest.cpp @@ -567,8 +567,8 @@ void HTTPWSTest::testLoadTortureODP() const auto sum_view_ids = loadTorture(testname, "empty.odp", thread_count, max_jitter_ms); // For ODP the view-id is always odd, and we expect not to skip any ids. - const auto number_of_loads = thread_count; - const int exp_sum_view_ids = number_of_loads * number_of_loads; // Odd view-ids only. + const int number_of_loads = thread_count; + const int exp_sum_view_ids = number_of_loads * (number_of_loads - 1) / 2; // 0-based view-ids. CPPUNIT_ASSERT_EQUAL(exp_sum_view_ids, sum_view_ids); } commit 00c2c968cabb48ccb6ad1c5a39199d0c39a9c05e Author: Michael Meeks <michael.me...@collabora.com> Date: Fri May 18 18:32:33 2018 +0100 Remove test monitor. Change-Id: I55f93ffec68745b194a778b541db1011962f735d diff --git a/loolwsd.xml.in b/loolwsd.xml.in index fea005b4f..ed275c016 100644 --- a/loolwsd.xml.in +++ b/loolwsd.xml.in @@ -114,7 +114,6 @@ </admin_console> <monitors desc="Addresses of servers we connect to on start for monitoring"> - <monitor>ws://localhost:9042/foo</monitor> </monitors> </config> commit aaaf7352b429cfe0a777c8492952fc302cc763b7 Author: Michael Meeks <michael.me...@collabora.com> Date: Fri May 18 16:05:36 2018 +0100 Shutdown kit process if connection dies. Change-Id: I34e627a03a9859ccd3ae9f9925fe6ab080697b72 diff --git a/kit/Kit.cpp b/kit/Kit.cpp index ef22214cc..83becc634 100644 --- a/kit/Kit.cpp +++ b/kit/Kit.cpp @@ -2034,6 +2034,12 @@ protected: LOG_ERR("Bad or unknown token [" << tokens[0] << "]"); } } + + void onDisconnect() override + { + LOG_WRN("Kit connection lost without exit arriving from wsd"); + TerminationFlag = true; + } }; void documentViewCallback(const int type, const char* payload, void* data) commit d8ee19c7f601d1f066b8bf8115c5733d47f0f852 Author: Michael Meeks <michael.me...@collabora.com> Date: Fri May 18 15:55:31 2018 +0100 Count kits better for crash testing. Change-Id: Icf04d4124e2b538886c8568358e94d00baeefeb4 diff --git a/test/httpcrashtest.cpp b/test/httpcrashtest.cpp index 0f8b1c3fd..29fd0c447 100644 --- a/test/httpcrashtest.cpp +++ b/test/httpcrashtest.cpp @@ -243,6 +243,7 @@ void killPids(const std::vector<int> &pids) void HTTPCrashTest::killLoKitProcesses() { killPids(getKitPids()); + InitialLoolKitCount = 1; // non-intuitive but it will arrive soon. } void HTTPCrashTest::killForkitProcess() commit 7513203daa9ab36f139f159283050632896ecb0e Author: Michael Meeks <michael.me...@collabora.com> Date: Fri May 18 14:32:26 2018 +0100 Make PNG tile tests more robust. Change-Id: Id7afcfe9b29b2d5544e296b13f04c195d35655b5 diff --git a/test/TileCacheTests.cpp b/test/TileCacheTests.cpp index 2231dfa9e..cc23b49c7 100644 --- a/test/TileCacheTests.cpp +++ b/test/TileCacheTests.cpp @@ -694,6 +694,10 @@ void TileCacheTests::testLoad12ods() { CPPUNIT_FAIL(exc.displayText()); } + catch (...) + { + CPPUNIT_FAIL("Unexpected exception thrown during ods load"); + } } void TileCacheTests::checkBlackTile(std::stringstream& tile) @@ -737,7 +741,13 @@ void TileCacheTests::checkBlackTiles(std::shared_ptr<LOOLWebSocket>& socket, con const auto req = "tile part=0 width=256 height=256 tileposx=0 tileposy=253440 tilewidth=3840 tileheight=3840"; sendTextFrame(socket, req); - const auto tile = getResponseMessage(socket, "tile:", name); + const std::vector<char> tile = getResponseMessage(socket, "tile:", name); + if (!tile.size()) + { + CPPUNIT_FAIL("No tile returned to checkBlackTiles - failed load ?"); + return; + } + const std::string firstLine = LOOLProtocol::getFirstLine(tile); #if 0 commit 72760589adeef70e484873eb08729487c7205358 Author: Jan Holesovsky <ke...@collabora.com> Date: Fri May 18 13:00:16 2018 +0200 Split close(bool) into close() and terminate(). The bool flag was causing 2 complete separate code paths anyway. Also remove stop(), calling stop() followed by close() made no difference. Change-Id: Ica4c887b0324390d4e006a26eb4119bd5ab08723 diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp index 2b08c4882..7654bc763 100644 --- a/wsd/DocumentBroker.cpp +++ b/wsd/DocumentBroker.cpp @@ -1599,10 +1599,7 @@ void DocumentBroker::terminateChild(const std::string& closeReason) { LOG_INF("Terminating child [" << getPid() << "] of doc [" << _docKey << "]."); - // First flag to stop as it might be waiting on our lock - // to process some incoming message. - _childProcess->stop(); - _childProcess->close(false); + _childProcess->close(); } stop(closeReason); diff --git a/wsd/DocumentBroker.hpp b/wsd/DocumentBroker.hpp index 4d7d0bd6e..92c39b2e5 100644 --- a/wsd/DocumentBroker.hpp +++ b/wsd/DocumentBroker.hpp @@ -78,63 +78,57 @@ public: ~ChildProcess() { - if (_pid > 0) - { - LOG_DBG("~ChildProcess dtor [" << _pid << "]."); - close(true); + if (_pid <= 0) + return; + + LOG_DBG("~ChildProcess dtor [" << _pid << "]."); + terminate(); + + // No need for the socket anymore. + _ws.reset(); + _socket.reset(); - // No need for the socket anymore. - _ws.reset(); - _socket.reset(); - } } void setDocumentBroker(const std::shared_ptr<DocumentBroker>& docBroker); std::shared_ptr<DocumentBroker> getDocumentBroker() const { return _docBroker.lock(); } - void stop() + /// Let the child close a nice way. + void close() { - // Request the child to exit. + if (_pid < 0) + return; + try { + LOG_DBG("Closing ChildProcess [" << _pid << "]."); + + // Request the child to exit if (isAlive()) { LOG_DBG("Stopping ChildProcess [" << _pid << "]"); sendTextFrame("exit"); } + + // Shutdown the socket. + if (_ws) + _ws->shutdown(); } - catch (const std::exception&) + catch (const std::exception& ex) { - // Already logged in sendTextFrame. + LOG_ERR("Error while closing child process: " << ex.what()); } + + _pid = -1; } - void close(const bool rude) + /// Kill or abandon the child. + void terminate() { if (_pid < 0) return; - try - { - LOG_DBG("Closing ChildProcess [" << _pid << "]."); - - if (!rude) - { - // First mark to stop the thread so it knows it's intentional. - stop(); - - // Shutdown the socket. - if (_ws) - _ws->shutdown(); - } - } - catch (const std::exception& ex) - { - LOG_ERR("Error while closing child process: " << ex.what()); - } - - // Kill or abandon the child. - if (rude && _pid != -1 && kill(_pid, 0) == 0) + if (::kill(_pid, 0) == 0) { LOG_INF("Killing child [" << _pid << "]."); if (!SigUtil::killChild(_pid)) @@ -179,7 +173,7 @@ public: { try { - return _pid > 1 && _ws && kill(_pid, 0) == 0; + return _pid > 1 && _ws && ::kill(_pid, 0) == 0; } catch (const std::exception&) { diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp index 4c5a7e3f4..4831aea33 100644 --- a/wsd/LOOLWSD.cpp +++ b/wsd/LOOLWSD.cpp @@ -2816,7 +2816,7 @@ int LOOLWSD::innerMain() LOG_INF("Requesting child processes to terminate."); for (auto& child : NewChildren) { - child->close(true); + child->terminate(); } #ifndef KIT_IN_PROCESS commit 6ba88372b9cee8e2838e70d8342dbfc5dc29bfc2 Author: Jan Holesovsky <ke...@collabora.com> Date: Thu May 17 20:47:37 2018 +0200 Before we kill the child, check it exists, ie. kill(pid, 0) == 0. Also warn when anything was left out. Without this, we leave abandoned children around. Change-Id: I293a530ffceeb7f6bdc0cc775335c782945de6e7 diff --git a/common/SigUtil.cpp b/common/SigUtil.cpp index 48899f378..76e7b44f6 100644 --- a/common/SigUtil.cpp +++ b/common/SigUtil.cpp @@ -284,6 +284,7 @@ namespace SigUtil sigaction(SIGUSR1, &action, nullptr); } + /// Kill the given pid with SIGTERM. Returns true when the pid does not exist any more. bool killChild(const int pid) { LOG_DBG("Killing PID: " << pid); diff --git a/wsd/DocumentBroker.hpp b/wsd/DocumentBroker.hpp index 58af3c913..4d7d0bd6e 100644 --- a/wsd/DocumentBroker.hpp +++ b/wsd/DocumentBroker.hpp @@ -134,10 +134,10 @@ public: } // Kill or abandon the child. - if (_pid != -1 && rude && kill(_pid, 0) != 0 && errno != ESRCH) + if (rude && _pid != -1 && kill(_pid, 0) == 0) { LOG_INF("Killing child [" << _pid << "]."); - if (SigUtil::killChild(_pid)) + if (!SigUtil::killChild(_pid)) { LOG_ERR("Cannot terminate lokit [" << _pid << "]. Abandoning."); } commit bcc6a19c6e8bbd66c3a6f2a22d9d799384b73ddf Author: Jan Holesovsky <ke...@collabora.com> Date: Wed Apr 25 17:38:55 2018 +0200 Paste: Prefer text/rtf mimetype when present. Change-Id: Id4bad2d6b09b3b14e64059a942a50ce61f8f4ea4 diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js index ce5afa643..49aa853c6 100644 --- a/loleaflet/src/layer/tile/TileLayer.js +++ b/loleaflet/src/layer/tile/TileLayer.js @@ -1982,48 +1982,51 @@ L.TileLayer = L.GridLayer.extend({ if (preferInternal === true) { var pasteString = dataTransfer.getData('text/plain'); if (!pasteString) { - pasteString = window.clipboardData.getData('Text'); + pasteString = dataTransfer.getData('Text'); // IE 11 } - if (pasteString === this._selectionTextHash) { + if (pasteString && pasteString === this._selectionTextHash) { this._map._socket.sendMessage('uno .uno:Paste'); - return + return; } } - // handle content var types = dataTransfer.types; - var hasHTML = false; - for (var t = 0; !hasHTML && t < types.length; t++) { - if (types[t] === 'text/html') { - hasHTML = true; - } - } - var handled = false; - for (t = 0; !handled && t < types.length; t++) { - var type = types[t]; - if (type === 'text/html') { - this._map._socket.sendMessage('paste mimetype=text/html\n' + dataTransfer.getData(type)); - handled = true; - } - else if ((type === 'text/plain' || type ==='Text') && !hasHTML) { - this._map._socket.sendMessage('paste mimetype=text/plain;charset=utf-8\n' + dataTransfer.getData(type)); - handled = true; - } - else if (type === 'Files') { + // first try to transfer images + // TODO if we have both Files and a normal mimetype, should we handle + // both, or prefer one or the other? + for (var t = 0; t < types.length; ++t) { + if (types[t] === 'Files') { var files = dataTransfer.files; - for (var i = 0; i < files.length; ++i) { - var file = files[i]; + for (var f = 0; f < files.length; ++f) { + var file = files[f]; if (file.type.match(/image.*/)) { var reader = new FileReader(); reader.onload = this._onFileLoadFunc(file); reader.readAsArrayBuffer(file); - handled = true; } } } } + + // now try various mime types + var mimeTypes = [ + ['text/rtf', 'text/rtf'], + ['text/html', 'text/html'], + ['text/plain', 'text/plain;charset=utf-8'], + ['Text', 'text/plain;charset=utf-8'] + ]; + + for (var i = 0; i < mimeTypes.length; ++i) { + for (t = 0; t < types.length; ++t) { + if (mimeTypes[i][0] === types[t]) { + var blob = new Blob(['paste mimetype=' + mimeTypes[i][1] + '\n', dataTransfer.getData(types[t])]); + this._map._socket.sendMessage(blob); + return; + } + } + } }, _onFileLoadFunc: function(file) { commit bba9e5bbfca29b5e71fa2bb258cb7cfb2bb76a99 Author: Jan Holesovsky <ke...@collabora.com> Date: Wed Apr 25 16:06:08 2018 +0200 Paste: Share the code with Drop, to allow rich content pasting. Change-Id: I4d80421786369388b8a1a094fe7633d525fa3f08 diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js index c5c8bf413..ce5afa643 100644 --- a/loleaflet/src/layer/tile/TileLayer.js +++ b/loleaflet/src/layer/tile/TileLayer.js @@ -14,16 +14,6 @@ if (typeof String.prototype.startsWith !== 'function') { } L.Compatibility = { - clipboardGet: function (event) { - var text = null; - if (event.clipboardData) { // Standard - text = event.clipboardData.getData('text/plain'); - } - else if (window.clipboardData) { // IE 11 - text = window.clipboardData.getData('Text'); - } - return text; - }, clipboardSet: function (event, text) { if (event.clipboardData) { // Standard event.clipboardData.setData('text/plain', text); @@ -1956,16 +1946,12 @@ L.TileLayer = L.GridLayer.extend({ _onPaste: function (e) { e = e.originalEvent; e.preventDefault(); - var pasteString = L.Compatibility.clipboardGet(e); - if (pasteString === 'false' || !pasteString || pasteString === this._selectionTextHash) { - // If there is nothing to paste in clipboard, no harm in - // issuing a .uno:Paste in case there is something internally copied in the document - // or if the content of the clipboard did not change, we surely must do a rich paste - // instead of a normal paste - this._map._socket.sendMessage('uno .uno:Paste'); + + if (e.clipboardData) { // Standard + this._dataTransferToDocument(e.clipboardData, /* preferInternal = */ true); } - else { - this._map._socket.sendMessage('paste mimetype=text/plain;charset=utf-8\n' + pasteString); + else if (window.clipboardData) { // IE 11 + this._dataTransferToDocument(window.clipboardData, /* preferInternal = */ true); } }, @@ -1988,8 +1974,25 @@ L.TileLayer = L.GridLayer.extend({ e = e.originalEvent; e.preventDefault(); + this._dataTransferToDocument(e.dataTransfer, /* preferInternal = */ false); + }, + + _dataTransferToDocument: function (dataTransfer, preferInternal) { + // for the paste, we might prefer the internal LOK's copy/paste + if (preferInternal === true) { + var pasteString = dataTransfer.getData('text/plain'); + if (!pasteString) { + pasteString = window.clipboardData.getData('Text'); + } + + if (pasteString === this._selectionTextHash) { + this._map._socket.sendMessage('uno .uno:Paste'); + return + } + } + // handle content - var types = e.dataTransfer.types; + var types = dataTransfer.types; var hasHTML = false; for (var t = 0; !hasHTML && t < types.length; t++) { if (types[t] === 'text/html') { @@ -2001,15 +2004,15 @@ L.TileLayer = L.GridLayer.extend({ for (t = 0; !handled && t < types.length; t++) { var type = types[t]; if (type === 'text/html') { - this._map._socket.sendMessage('paste mimetype=text/html\n' + e.dataTransfer.getData(type)); + this._map._socket.sendMessage('paste mimetype=text/html\n' + dataTransfer.getData(type)); handled = true; } - else if (type === 'text/plain' && !hasHTML) { - this._map._socket.sendMessage('paste mimetype=text/plain;charset=utf-8\n' + e.dataTransfer.getData(type)); + else if ((type === 'text/plain' || type ==='Text') && !hasHTML) { + this._map._socket.sendMessage('paste mimetype=text/plain;charset=utf-8\n' + dataTransfer.getData(type)); handled = true; } else if (type === 'Files') { - var files = e.dataTransfer.files; + var files = dataTransfer.files; for (var i = 0; i < files.length; ++i) { var file = files[i]; if (file.type.match(/image.*/)) { commit 6ac56c88ebbbc11c96ae9b33001237b1604b5c3b Author: Michael Meeks <michael.me...@collabora.com> Date: Sun May 13 12:44:39 2018 +0100 seccomp: allow socket shutdown in kit process. Change-Id: Ie11f5eb278bcba8dcf13d6f095de2ffd6d23fcb3 diff --git a/common/Seccomp.cpp b/common/Seccomp.cpp index e49c4d5e8..fea452506 100644 --- a/common/Seccomp.cpp +++ b/common/Seccomp.cpp @@ -133,7 +133,6 @@ bool lockdown(Type type) KILL_SYSCALL(getitimer), KILL_SYSCALL(setitimer), KILL_SYSCALL(sendfile), - KILL_SYSCALL(shutdown), KILL_SYSCALL(listen), // server sockets KILL_SYSCALL(accept), // server sockets #if 0 commit bb508aedbce646dbff3da8c5c2b9b92d56c88ded Author: Jan Holesovsky <ke...@collabora.com> Date: Fri May 11 19:15:16 2018 +0200 Post the message to the poll thread. Change-Id: Ibd28090a420b5396b64fdfe676bef8cf06991116 diff --git a/kit/Kit.cpp b/kit/Kit.cpp index 2bb229c62..ef22214cc 100644 --- a/kit/Kit.cpp +++ b/kit/Kit.cpp @@ -694,6 +694,7 @@ public: const std::string& docId, const std::string& url, std::shared_ptr<TileQueue> tileQueue, + SocketPoll& socketPoll, const std::shared_ptr<WebSocketHandler>& websocketHandler) : _loKit(loKit), _jailId(jailId), @@ -701,6 +702,7 @@ public: _docId(docId), _url(url), _tileQueue(std::move(tileQueue)), + _socketPoll(socketPoll), _websocketHandler(websocketHandler), _docPassword(""), _haveDocPassword(false), @@ -735,6 +737,20 @@ public: const std::string& getUrl() const { return _url; } + /// Post the message in the correct thread. + bool postMessage(const std::shared_ptr<std::vector<char>>& message, const WSOpCode code) const + { + LOG_TRC("postMessage called with: " << getAbbreviatedMessage(message->data(), message->size())); + if (!_websocketHandler) + { + LOG_ERR("Child Doc: Bad socket while sending [" << getAbbreviatedMessage(message->data(), message->size()) << "]."); + return false; + } + + _socketPoll.addCallback([=] { _websocketHandler->sendMessage(message->data(), message->size(), code); }); + return true; + } + bool createSession(const std::string& sessionId) { std::unique_lock<std::mutex> lock(_mutex); @@ -849,9 +865,8 @@ public: LOG_INF("setDocumentPassword returned"); } - void renderTile(const std::vector<std::string>& tokens, const std::shared_ptr<WebSocketHandler>& websocketHandler) + void renderTile(const std::vector<std::string>& tokens) { - assert(websocketHandler && "Expected a non-null websocket."); TileDesc tile = TileDesc::parse(tokens); size_t pixmapDataSize = 4 * tile.getWidth() * tile.getHeight(); @@ -905,12 +920,12 @@ public: if (_docWatermark) _docWatermark->blending(pixmap.data(), 0, 0, pixelWidth, pixelHeight, pixelWidth, pixelHeight, mode); - std::vector<char> output; - output.reserve(response.size() + pixmapDataSize); - output.resize(response.size()); - std::memcpy(output.data(), response.data(), response.size()); + std::shared_ptr<std::vector<char>> output = std::make_shared<std::vector<char>>(); + output->reserve(response.size() + pixmapDataSize); + output->resize(response.size()); + std::memcpy(output->data(), response.data(), response.size()); - if (!_pngCache.encodeBufferToPNG(pixmap.data(), tile.getWidth(), tile.getHeight(), output, mode, hash, wid, oldWireId)) + if (!_pngCache.encodeBufferToPNG(pixmap.data(), tile.getWidth(), tile.getHeight(), *output, mode, hash, wid, oldWireId)) { //FIXME: Return error. //sendTextFrame("error: cmd=tile kind=failure"); @@ -919,13 +934,12 @@ public: return; } - LOG_TRC("Sending render-tile response (" << output.size() << " bytes) for: " << response); - websocketHandler->sendMessage(output.data(), output.size(), WSOpCode::Binary); + LOG_TRC("Sending render-tile response (" << output->size() << " bytes) for: " << response); + postMessage(output, WSOpCode::Binary); } - void renderCombinedTiles(const std::vector<std::string>& tokens, const std::shared_ptr<WebSocketHandler>& websocketHandler) + void renderCombinedTiles(const std::vector<std::string>& tokens) { - assert(websocketHandler && "Expected a non-null websocket."); TileCombined tileCombined = TileCombined::parse(tokens); auto& tiles = tileCombined.getTiles(); @@ -1044,12 +1058,12 @@ public: const auto tileMsg = ADD_DEBUG_RENDERID(tileCombined.serialize("tilecombine:")) + "\n"; LOG_TRC("Sending back painted tiles for " << tileMsg); - std::vector<char> response; - response.resize(tileMsg.size() + output.size()); - std::copy(tileMsg.begin(), tileMsg.end(), response.begin()); - std::copy(output.begin(), output.end(), response.begin() + tileMsg.size()); + std::shared_ptr<std::vector<char>> response = std::make_shared<std::vector<char>>(); + response->resize(tileMsg.size() + output.size()); + std::copy(tileMsg.begin(), tileMsg.end(), response->begin()); + std::copy(output.begin(), output.end(), response->begin() + tileMsg.size()); - websocketHandler->sendMessage(response.data(), response.size(), WSOpCode::Binary); + postMessage(response, WSOpCode::Binary); } bool sendTextFrame(const std::string& message) @@ -1061,14 +1075,11 @@ public: { try { - if (!_websocketHandler) - { - LOG_ERR("Child Doc: Bad socket while sending [" << getAbbreviatedMessage(buffer, length) << "]."); - return false; - } + std::shared_ptr<std::vector<char>> message = std::make_shared<std::vector<char>>(); + message->resize(length); + std::memcpy(message->data(), buffer, length); - _websocketHandler->sendMessage(buffer, length, opCode); - return true; + return postMessage(message, opCode); } catch (const Exception& exc) { @@ -1768,11 +1779,11 @@ private: if (tokens[0] == "tile") { - renderTile(tokens, _websocketHandler); + renderTile(tokens); } else if (tokens[0] == "tilecombine") { - renderCombinedTiles(tokens, _websocketHandler); + renderCombinedTiles(tokens); } else if (LOOLProtocol::getFirstToken(tokens[0], '-') == "child") { @@ -1895,6 +1906,7 @@ private: std::shared_ptr<lok::Document> _loKitDocument; std::shared_ptr<TileQueue> _tileQueue; + SocketPoll& _socketPoll; std::shared_ptr<WebSocketHandler> _websocketHandler; PngCache _pngCache; @@ -1937,14 +1949,16 @@ class KitWebSocketHandler final : public WebSocketHandler, public std::enable_sh std::string _socketName; std::shared_ptr<lok::Office> _loKit; std::string _jailId; + SocketPoll& _socketPoll; public: - KitWebSocketHandler(const std::string& socketName, const std::shared_ptr<lok::Office>& loKit, const std::string& jailId) : + KitWebSocketHandler(const std::string& socketName, const std::shared_ptr<lok::Office>& loKit, const std::string& jailId, SocketPoll& socketPoll) : WebSocketHandler(/* isClient = */ true), _queue(std::make_shared<TileQueue>()), _socketName(socketName), _loKit(loKit), - _jailId(jailId) + _jailId(jailId), + _socketPoll(socketPoll) { } @@ -1980,7 +1994,7 @@ protected: if (!document) { - document = std::make_shared<Document>(_loKit, _jailId, docKey, docId, url, _queue, shared_from_this()); + document = std::make_shared<Document>(_loKit, _jailId, docKey, docId, url, _queue, _socketPoll, shared_from_this()); } // Validate and create session. @@ -2271,7 +2285,7 @@ void lokit_main(const std::string& childRoot, SocketPoll mainKit("kit"); - mainKit.insertNewWebSocketSync(uri, std::make_shared<KitWebSocketHandler>("child_ws_" + pid, loKit, jailId)); + mainKit.insertNewWebSocketSync(uri, std::make_shared<KitWebSocketHandler>("child_ws_" + pid, loKit, jailId, mainKit)); LOG_INF("New kit client websocket inserted."); while (!TerminationFlag) commit 8eb60a33eb1afcdae377c87e1db656f80a4ccf29 Author: Jan Holesovsky <ke...@collabora.com> Date: Wed May 9 20:25:58 2018 +0200 Use std::shared_ptr consistently. Change-Id: I6bf3ff7de47010fd78fab26a5a318bde21c1f153 diff --git a/net/Socket.hpp b/net/Socket.hpp index a9ace0966..a3c194d83 100644 --- a/net/Socket.hpp +++ b/net/Socket.hpp @@ -704,11 +704,11 @@ protected: class StreamSocket : public Socket, public std::enable_shared_from_this<StreamSocket> { public: - /// Create a StreamSocket from native FD and take ownership of handler instance. + /// Create a StreamSocket from native FD. StreamSocket(const int fd, bool /* isClient */, - std::shared_ptr<SocketHandlerInterface> socketHandler) : + const std::shared_ptr<SocketHandlerInterface> socketHandler) : Socket(fd), - _socketHandler(std::move(socketHandler)), + _socketHandler(socketHandler), _bytesSent(0), _bytesRecvd(0), _wsState(WSState::HTTP), diff --git a/net/WebSocketHandler.hpp b/net/WebSocketHandler.hpp index c0628b456..1294a51ec 100644 --- a/net/WebSocketHandler.hpp +++ b/net/WebSocketHandler.hpp @@ -130,7 +130,7 @@ public: if (len == 0) return false; // avoid logging. - LOG_TRC("#" << socket->getFD() << ": Incoming WebSocket data of " << len << " bytes."); + LOG_TRC("#" << socket->getFD() << ": Incoming WebSocket data of " << len << " bytes: " << std::string(socket->_inBuffer.data(), socket->_inBuffer.size())); if (len < 2) // partial read return false; diff --git a/tools/WebSocketDump.cpp b/tools/WebSocketDump.cpp index 202bff3f7..777a2c82e 100644 --- a/tools/WebSocketDump.cpp +++ b/tools/WebSocketDump.cpp @@ -205,9 +205,9 @@ public: { #if ENABLE_SSL if (_isSSL) - return StreamSocket::create<SslStreamSocket>(physicalFd, false, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); + return StreamSocket::create<SslStreamSocket>(physicalFd, false, std::make_shared<ClientRequestDispatcher>()); #endif - return StreamSocket::create<StreamSocket>(physicalFd, false, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); + return StreamSocket::create<StreamSocket>(physicalFd, false, std::make_shared<ClientRequestDispatcher>()); } }; diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp index 1f701e19a..4c5a7e3f4 100644 --- a/wsd/LOOLWSD.cpp +++ b/wsd/LOOLWSD.cpp @@ -2394,8 +2394,7 @@ class PlainSocketFactory final : public SocketFactory std::shared_ptr<Socket> socket = StreamSocket::create<StreamSocket>( - fd, false, std::unique_ptr<SocketHandlerInterface>{ - new ClientRequestDispatcher }); + fd, false, std::make_shared<ClientRequestDispatcher>()); return socket; } @@ -2412,8 +2411,7 @@ class SslSocketFactory final : public SocketFactory fd = Delay::create(SimulatedLatencyMs, physicalFd); return StreamSocket::create<SslStreamSocket>( - fd, false, std::unique_ptr<SocketHandlerInterface>{ - new ClientRequestDispatcher }); + fd, false, std::make_shared<ClientRequestDispatcher>()); } }; #endif @@ -2423,7 +2421,7 @@ class PrisonerSocketFactory final : public SocketFactory std::shared_ptr<Socket> create(const int fd) override { // No local delay. - return StreamSocket::create<StreamSocket>(fd, false, std::unique_ptr<SocketHandlerInterface>{ new PrisonerRequestDispatcher }); + return StreamSocket::create<StreamSocket>(fd, false, std::make_shared<PrisonerRequestDispatcher>()); } }; commit 7158b2eddce4cdf236032cee194a03d7f8c086e8 Author: Jan Holesovsky <ke...@collabora.com> Date: Mon May 7 15:09:40 2018 +0200 Use correct path in the client websockets. Change-Id: Ie0bf6646ff3f6e6cf99b505143a416c86a3a33b8 diff --git a/common/Common.hpp b/common/Common.hpp index b58c2ca75..978ef2c18 100644 --- a/common/Common.hpp +++ b/common/Common.hpp @@ -29,10 +29,10 @@ constexpr long READ_BUFFER_SIZE = 64 * 1024; /// or as intentionally flooding the server. constexpr int MAX_MESSAGE_SIZE = 2 * 1024 * READ_BUFFER_SIZE; -constexpr auto JAILED_DOCUMENT_ROOT = "/user/docs/"; -constexpr auto CHILD_URI = "/loolws/child?"; -constexpr auto NEW_CHILD_URI = "/loolws/newchild?"; -constexpr auto LO_JAIL_SUBPATH = "lo"; +constexpr const char JAILED_DOCUMENT_ROOT[] = "/user/docs/"; +constexpr const char CHILD_URI[] = "/loolws/child?"; +constexpr const char NEW_CHILD_URI[] = "/loolws/newchild"; +constexpr const char LO_JAIL_SUBPATH[] = "lo"; /// The HTTP response User-Agent. constexpr auto HTTP_AGENT_STRING = "LOOLWSD HTTP Agent " LOOLWSD_VERSION; diff --git a/kit/Kit.cpp b/kit/Kit.cpp index d94166806..2bb229c62 100644 --- a/kit/Kit.cpp +++ b/kit/Kit.cpp @@ -2253,25 +2253,24 @@ void lokit_main(const std::string& childRoot, static const std::string pid = std::to_string(Process::id()); - std::string requestUrl = NEW_CHILD_URI; - requestUrl += "pid=" + pid + "&jailid=" + jailId; + Poco::URI uri("ws://127.0.0.1"); + uri.setPort(MasterPortNumber); + uri.setPath(NEW_CHILD_URI); + uri.addQueryParameter("pid", std::to_string(Process::id())); + uri.addQueryParameter("jailid", jailId); + if (queryVersion) { char* versionInfo = loKit->getVersionInfo(); std::string versionString(versionInfo); if (displayVersion) std::cout << "office version details: " << versionString << std::endl; - std::string encodedVersionStr; - URI::encode(versionString, "", encodedVersionStr); - requestUrl += "&version=" + encodedVersionStr; + uri.addQueryParameter("version", versionString); free(versionInfo); } SocketPoll mainKit("kit"); - Poco::URI uri("ws://127.0.0.1"); - uri.setPort(MasterPortNumber); - mainKit.insertNewWebSocketSync(uri, std::make_shared<KitWebSocketHandler>("child_ws_" + pid, loKit, jailId)); LOG_INF("New kit client websocket inserted."); diff --git a/net/Socket.cpp b/net/Socket.cpp index c7c265980..f9915c773 100644 --- a/net/Socket.cpp +++ b/net/Socket.cpp @@ -185,7 +185,7 @@ void SocketPoll::insertNewWebSocketSync(const Poco::URI &uri, const std::shared_ // send Sec-WebSocket-Key: <hmm> ... Sec-WebSocket-Protocol: chat, Sec-WebSocket-Version: 13 std::ostringstream oss; - oss << "GET " << uri.getHost() << " HTTP/1.1\r\n" + oss << "GET " << uri.getPathAndQuery() << " HTTP/1.1\r\n" "Connection:Upgrade\r\n" "User-Foo: Adminbits\r\n" "Sec-WebSocket-Key: GAcwqP21iVOY2yKefQ64c0yVN5M=\r\n" diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp index cccdf1a71..1f701e19a 100644 --- a/wsd/LOOLWSD.cpp +++ b/wsd/LOOLWSD.cpp @@ -1613,14 +1613,15 @@ private: return; LOG_TRC("Child connection with URI [" << request.getURI() << "]."); - if (request.getURI().find(NEW_CHILD_URI) != 0) + Poco::URI requestURI(request.getURI()); + if (requestURI.getPath() != NEW_CHILD_URI) { LOG_ERR("Invalid incoming URI."); return; } // New Child is spawned. - const auto params = Poco::URI(request.getURI()).getQueryParameters(); + const Poco::URI::QueryParameters params = requestURI.getQueryParameters(); Poco::Process::PID pid = -1; std::string jailId; for (const auto& param : params) commit d2b0465fa51dab381ed12115870e0696d92dc92e Author: Jan Holesovsky <ke...@collabora.com> Date: Fri May 4 18:47:33 2018 +0200 Move the functionality from connectToMonitor() to SocketPoll. Change-Id: Iab2ac09638323f5e59f7a2ea0d880f52989ad64d diff --git a/kit/ChildSession.hpp b/kit/ChildSession.hpp index d8ae37b10..b1cd90133 100644 --- a/kit/ChildSession.hpp +++ b/kit/ChildSession.hpp @@ -70,7 +70,7 @@ public: virtual std::shared_ptr<TileQueue>& getTileQueue() = 0; - virtual bool sendFrame(const char* buffer, int length, int flags = Poco::Net::WebSocket::FRAME_TEXT) = 0; + virtual bool sendFrame(const char* buffer, int length, WSOpCode opCode = WSOpCode::Text) = 0; }; struct RecordedEvent @@ -160,15 +160,15 @@ public: bool sendTextFrame(const char* buffer, int length) override { const auto msg = "client-" + getId() + ' ' + std::string(buffer, length); - const auto lock = getLock(); - return _docManager.sendFrame(msg.data(), msg.size(), Poco::Net::WebSocket::FRAME_TEXT); + const std::unique_lock<std::mutex> lock = getLock(); + return _docManager.sendFrame(msg.data(), msg.size(), WSOpCode::Text); } bool sendBinaryFrame(const char* buffer, int length) override { const auto msg = "client-" + getId() + ' ' + std::string(buffer, length); - const auto lock = getLock(); - return _docManager.sendFrame(msg.data(), msg.size(), Poco::Net::WebSocket::FRAME_BINARY); + const std::unique_lock<std::mutex> lock = getLock(); + return _docManager.sendFrame(msg.data(), msg.size(), WSOpCode::Binary); } using Session::sendTextFrame; diff --git a/kit/Kit.cpp b/kit/Kit.cpp index 9cfce9b93..d94166806 100644 --- a/kit/Kit.cpp +++ b/kit/Kit.cpp @@ -920,7 +920,7 @@ public: } LOG_TRC("Sending render-tile response (" << output.size() << " bytes) for: " << response); - websocketHandler->sendMessage(output.data(), output.size(), WebSocket::FRAME_BINARY); + websocketHandler->sendMessage(output.data(), output.size(), WSOpCode::Binary); } void renderCombinedTiles(const std::vector<std::string>& tokens, const std::shared_ptr<WebSocketHandler>& websocketHandler) @@ -1049,7 +1049,7 @@ public: std::copy(tileMsg.begin(), tileMsg.end(), response.begin()); std::copy(output.begin(), output.end(), response.begin() + tileMsg.size()); - websocketHandler->sendMessage(response.data(), response.size(), WebSocket::FRAME_BINARY); + websocketHandler->sendMessage(response.data(), response.size(), WSOpCode::Binary); } bool sendTextFrame(const std::string& message) @@ -1057,7 +1057,7 @@ public: return sendFrame(message.data(), message.size()); } - bool sendFrame(const char* buffer, int length, int flags = Poco::Net::WebSocket::FRAME_TEXT) override + bool sendFrame(const char* buffer, int length, WSOpCode opCode = WSOpCode::Text) override { try { @@ -1067,7 +1067,7 @@ public: return false; } - _websocketHandler->sendMessage(buffer, length, flags); + _websocketHandler->sendMessage(buffer, length, opCode); return true; } catch (const Exception& exc) @@ -2269,7 +2269,7 @@ void lokit_main(const std::string& childRoot, SocketPoll mainKit("kit"); - const Poco::URI uri("ws://127.0.0.1"); + Poco::URI uri("ws://127.0.0.1"); uri.setPort(MasterPortNumber); mainKit.insertNewWebSocketSync(uri, std::make_shared<KitWebSocketHandler>("child_ws_" + pid, loKit, jailId)); diff --git a/net/Socket.cpp b/net/Socket.cpp index 96ad7f7c4..c7c265980 100644 --- a/net/Socket.cpp +++ b/net/Socket.cpp @@ -20,10 +20,12 @@ #include <Poco/MemoryStream.h> #include <Poco/Net/HTTPRequest.h> #include <Poco/Net/HTTPResponse.h> +#include <Poco/URI.h> #include "SigUtil.hpp" #include "Socket.hpp" #include "ServerSocket.hpp" +#include "SslSocket.hpp" #include "WebSocketHandler.hpp" int SocketPoll::DefaultPollTimeoutMs = 5000; @@ -126,6 +128,98 @@ void SocketPoll::wakeupWorld() wakeup(fd); } +void SocketPoll::insertNewWebSocketSync(const Poco::URI &uri, const std::shared_ptr<SocketHandlerInterface>& websocketHandler) +{ + LOG_INF("Connecting to " << uri.getHost() << " : " << uri.getPort() << " : " << uri.getPath()); + + // FIXME: put this in a ClientSocket class ? + // FIXME: store the address there - and ... (so on) ... + struct addrinfo* ainfo = nullptr; + struct addrinfo hints; + std::memset(&hints, 0, sizeof(hints)); + int rc = getaddrinfo(uri.getHost().c_str(), + std::to_string(uri.getPort()).c_str(), + &hints, &ainfo); + std::string canonicalName; + bool isSSL = uri.getScheme() != "ws"; +#if !ENABLE_SSL + if (isSSL) + { + LOG_ERR("Error: wss for client websocket requested but SSL not compiled in."); + return; + } +#endif + + if (!rc && ainfo) + { + for (struct addrinfo* ai = ainfo; ai; ai = ai->ai_next) + { + if (ai->ai_canonname) + canonicalName = ai->ai_canonname; + + if (ai->ai_addrlen && ai->ai_addr) + { + int fd = socket(ai->ai_addr->sa_family, SOCK_STREAM | SOCK_NONBLOCK, 0); + int res = connect(fd, ai->ai_addr, ai->ai_addrlen); + // FIXME: SSL sockets presumably need some setup, checking etc. and ... =) + if (fd < 0 || (res < 0 && errno != EINPROGRESS)) + { + LOG_ERR("Failed to connect to " << uri.getHost()); + close(fd); + } + else + { + std::shared_ptr<StreamSocket> socket; +#if ENABLE_SSL + if (isSSL) + socket = StreamSocket::create<SslStreamSocket>(fd, true, websocketHandler); +#endif + if (!socket && !isSSL) + socket = StreamSocket::create<StreamSocket>(fd, true, websocketHandler); + + if (socket) + { + LOG_DBG("Connected to client websocket " << uri.getHost() << " #" << socket->getFD()); + + // cf. WebSocketHandler::upgradeToWebSocket (?) + // send Sec-WebSocket-Key: <hmm> ... Sec-WebSocket-Protocol: chat, Sec-WebSocket-Version: 13 + + std::ostringstream oss; + oss << "GET " << uri.getHost() << " HTTP/1.1\r\n" + "Connection:Upgrade\r\n" + "User-Foo: Adminbits\r\n" + "Sec-WebSocket-Key: GAcwqP21iVOY2yKefQ64c0yVN5M=\r\n" + "Upgrade:websocket\r\n" + "Accept-Encoding:gzip, deflate, br\r\n" + "Accept-Language:en\r\n" + "Cache-Control:no-cache\r\n" + "Pragma:no-cache\r\n" + "Sec-WebSocket-Extensions:permessage-deflate; client_max_window_bits\r\n" + "Sec-WebSocket-Key:fxTaWTEMVhq1PkWsMoLxGw==\r\n" + "Sec-WebSocket-Version:13\r\n" + "User-Agent: " << WOPI_AGENT_STRING << "\r\n" + "\r\n"; + socket->send(oss.str()); + websocketHandler->onConnect(socket); + insertNewSocket(socket); + } + else + { + LOG_ERR("Failed to allocate socket for client websocket " << uri.getHost()); + close(fd); + } + + break; + } + } + } + + freeaddrinfo(ainfo); + } + else + LOG_ERR("Failed to lookup client websocket host '" << uri.getHost() << "' skipping"); +} + void ServerSocket::dumpState(std::ostream& os) { os << "\t" << getFD() << "\t<accept>\n"; diff --git a/net/Socket.hpp b/net/Socket.hpp index d0c03071c..a9ace0966 100644 --- a/net/Socket.hpp +++ b/net/Socket.hpp @@ -46,6 +46,7 @@ namespace Poco class HTTPRequest; class HTTPResponse; } + class URI; } class Socket; @@ -307,6 +308,41 @@ private: std::thread::id _owner; }; +class StreamSocket; + +/// Interface that handles the actual incoming message. +class SocketHandlerInterface +{ +public: + virtual ~SocketHandlerInterface() {} + /// Called when the socket is newly created to + /// set the socket associated with this ResponseClient. + /// Will be called exactly once. + virtual void onConnect(const std::shared_ptr<StreamSocket>& socket) = 0; + + /// Called after successful socket reads. + virtual void handleIncomingMessage(SocketDisposition &disposition) = 0; + + /// Prepare our poll record; adjust @timeoutMaxMs downwards + /// for timeouts, based on current time @now. + /// @returns POLLIN and POLLOUT if output is expected. + virtual int getPollEvents(std::chrono::steady_clock::time_point now, + int &timeoutMaxMs) = 0; + + /// Do we need to handle a timeout ? + virtual void checkTimeout(std::chrono::steady_clock::time_point /* now */) {} + + /// Do some of the queued writing. + virtual void performWrites() = 0; + + /// Called when the is disconnected and will be destroyed. + /// Will be called exactly once. + virtual void onDisconnect() {} + + /// Append pretty printed internal state to a line + virtual void dumpState(std::ostream& os) { os << "\n"; } +}; + /// Handles non-blocking socket event polling. /// Only polls on N-Sockets and invokes callback and /// doesn't manage buffers or client data. @@ -534,6 +570,10 @@ public: } } + /// Inserts a new websocket to be polled. + /// NOTE: The DNS lookup is synchronous. + void insertNewWebSocketSync(const Poco::URI &uri, const std::shared_ptr<SocketHandlerInterface>& websocketHandler); + typedef std::function<void()> CallbackFn; /// Add a callback to be invoked in the polling thread @@ -660,41 +700,6 @@ protected: std::thread::id _owner; }; -class StreamSocket; - -/// Interface that handles the actual incoming message. -class SocketHandlerInterface -{ -public: - virtual ~SocketHandlerInterface() {} - /// Called when the socket is newly created to - /// set the socket associated with this ResponseClient. - /// Will be called exactly once. - virtual void onConnect(const std::shared_ptr<StreamSocket>& socket) = 0; - - /// Called after successful socket reads. - virtual void handleIncomingMessage(SocketDisposition &disposition) = 0; - - /// Prepare our poll record; adjust @timeoutMaxMs downwards - /// for timeouts, based on current time @now. - /// @returns POLLIN and POLLOUT if output is expected. - virtual int getPollEvents(std::chrono::steady_clock::time_point now, - int &timeoutMaxMs) = 0; - - /// Do we need to handle a timeout ? - virtual void checkTimeout(std::chrono::steady_clock::time_point /* now */) {} - - /// Do some of the queued writing. - virtual void performWrites() = 0; - - /// Called when the is disconnected and will be destroyed. - /// Will be called exactly once. - virtual void onDisconnect() {} - - /// Append pretty printed internal state to a line - virtual void dumpState(std::ostream& os) { os << "\n"; } -}; - /// A plain, non-blocking, data streaming socket. class StreamSocket : public Socket, public std::enable_shared_from_this<StreamSocket> { diff --git a/test/Makefile.am b/test/Makefile.am index 81346fe01..575a78ca5 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -25,6 +25,10 @@ noinst_LTLIBRARIES = \ MAGIC_TO_FORCE_SHLIB_CREATION = -rpath /dummy AM_LDFLAGS = -pthread -module $(MAGIC_TO_FORCE_SHLIB_CREATION) $(ZLIB_LIBS) +if ENABLE_SSL +AM_LDFLAGS += -lssl -lcrypto +endif + # We work around some of the mess of using the same sources both on # the server side and here in unit tests with conditional compilation # based on BUILDING_TESTS @@ -47,6 +51,10 @@ wsd_sources = \ ../common/Unit.cpp \ ../net/Socket.cpp +if ENABLE_SSL +wsd_sources += ../net/Ssl.cpp +endif + test_base_source = \ TileQueueTests.cpp \ WhiteBoxTests.cpp \ diff --git a/test/WhiteBoxTests.cpp b/test/WhiteBoxTests.cpp index ba8b1603b..a05d34f89 100644 --- a/test/WhiteBoxTests.cpp +++ b/test/WhiteBoxTests.cpp @@ -387,7 +387,7 @@ public: return _tileQueue; } - bool sendFrame(const char* /*buffer*/, int /*length*/, int /*flags*/) override + bool sendFrame(const char* /*buffer*/, int /*length*/, WSOpCode /*opCode*/) override { return true; } diff --git a/wsd/Admin.cpp b/wsd/Admin.cpp index c34a98aa9..0c195e419 100644 --- a/wsd/Admin.cpp +++ b/wsd/Admin.cpp @@ -658,95 +658,7 @@ public: void Admin::connectToMonitor(const Poco::URI &uri) { - LOG_INF("Connecting to monitor " << uri.getHost() << " : " << uri.getPort() << " : " << uri.getPath()); - - // FIXME: put this in a ClientSocket class ? - // FIXME: store the address there - and ... (so on) ... - struct addrinfo* ainfo = nullptr; - struct addrinfo hints; - std::memset(&hints, 0, sizeof(hints)); - int rc = getaddrinfo(uri.getHost().c_str(), - std::to_string(uri.getPort()).c_str(), - &hints, &ainfo); - std::string canonicalName; - bool isSSL = uri.getScheme() != "ws"; -#if !ENABLE_SSL - if (isSSL) - { - LOG_ERR("Error: wss for monitor requested but SSL not compiled in."); - return; - } -#endif - - if (!rc && ainfo) - { - for (struct addrinfo* ai = ainfo; ai; ai = ai->ai_next) - { - if (ai->ai_canonname) - canonicalName = ai->ai_canonname; - - if (ai->ai_addrlen && ai->ai_addr) - { - int fd = socket(ai->ai_addr->sa_family, SOCK_STREAM | SOCK_NONBLOCK, 0); - int res = connect(fd, ai->ai_addr, ai->ai_addrlen); - // FIXME: SSL sockets presumably need some setup, checking etc. and ... =) - if (fd < 0 || (res < 0 && errno != EINPROGRESS)) - { - LOG_ERR("Failed to connect to " << uri.getHost()); - close(fd); - } - else - { - std::shared_ptr<StreamSocket> socket; - std::shared_ptr<SocketHandlerInterface> handler = std::make_shared<MonitorSocketHandler>(this); -#if ENABLE_SSL - if (isSSL) - socket = StreamSocket::create<SslStreamSocket>(fd, true, handler); -#endif - if (!socket && !isSSL) - socket = StreamSocket::create<StreamSocket>(fd, true, handler); - - if (socket) - { - LOG_DBG("Connected to monitor " << uri.getHost() << " #" << socket->getFD()); - - // cf. WebSocketHandler::upgradeToWebSocket (?) - // send Sec-WebSocket-Key: <hmm> ... Sec-WebSocket-Protocol: chat, Sec-WebSocket-Version: 13 - - std::ostringstream oss; - oss << "GET " << uri.getHost() << " HTTP/1.1\r\n" - "Connection:Upgrade\r\n" - "User-Foo: Adminbits\r\n" - "Sec-WebSocket-Key: GAcwqP21iVOY2yKefQ64c0yVN5M=\r\n" - "Upgrade:websocket\r\n" - "Accept-Encoding:gzip, deflate, br\r\n" - "Accept-Language:en\r\n" - "Cache-Control:no-cache\r\n" - "Pragma:no-cache\r\n" - "Sec-WebSocket-Extensions:permessage-deflate; client_max_window_bits\r\n" - "Sec-WebSocket-Key:fxTaWTEMVhq1PkWsMoLxGw==\r\n" - "Sec-WebSocket-Version:13\r\n" - "User-Agent: " << WOPI_AGENT_STRING << "\r\n" - "\r\n"; - socket->send(oss.str()); - handler->onConnect(socket); - insertNewSocket(socket); - } - else - { - LOG_ERR("Failed to allocate socket for monitor " << uri.getHost()); - close(fd); - } - - break; - } - } - } - - freeaddrinfo(ainfo); - } - else - LOG_ERR("Failed to lookup monitor host '" << uri.getHost() << "' skipping"); + insertNewWebSocketSync(uri, std::make_shared<MonitorSocketHandler>(this)); } void Admin::start() commit 16e2998111d0b6b0b9fa2d919c707f2623c94e34 Author: Jan Holesovsky <ke...@collabora.com> Date: Fri May 4 18:32:42 2018 +0200 Change Kit to use the new client websocket code. Change-Id: Ib4e62ea618da5bd8992b51165b0d7ee955c61637 diff --git a/kit/Kit.cpp b/kit/Kit.cpp index 779deb151..9cfce9b93 100644 --- a/kit/Kit.cpp +++ b/kit/Kit.cpp @@ -57,18 +57,17 @@ #include "IoUtil.hpp" #include "KitHelper.hpp" #include "Kit.hpp" -#include "Protocol.hpp" -#include "LOOLWebSocket.hpp" -#include "Log.hpp" -#include "Png.hpp" -#include "Rectangle.hpp" -#include "TileDesc.hpp" -#include "Unit.hpp" -#include "UserMessages.hpp" -#include "Util.hpp" - -#include "common/SigUtil.hpp" -#include "common/Seccomp.hpp" +#include <Protocol.hpp> +#include <Log.hpp> +#include <Png.hpp> +#include <Rectangle.hpp> +#include <TileDesc.hpp> +#include <Unit.hpp> +#include <UserMessages.hpp> +#include <Util.hpp> + +#include <common/SigUtil.hpp> +#include <common/Seccomp.hpp> #ifdef FUZZER #include <kit/DummyLibreOfficeKit.hpp> @@ -695,14 +694,14 @@ public: const std::string& docId, const std::string& url, std::shared_ptr<TileQueue> tileQueue, - const std::shared_ptr<LOOLWebSocket>& ws) + const std::shared_ptr<WebSocketHandler>& websocketHandler) : _loKit(loKit), _jailId(jailId), _docKey(docKey), _docId(docId), _url(url), _tileQueue(std::move(tileQueue)), - _ws(ws), + _websocketHandler(websocketHandler), _docPassword(""), _haveDocPassword(false), _isDocPasswordProtected(false), @@ -850,10 +849,10 @@ public: LOG_INF("setDocumentPassword returned"); } - void renderTile(const std::vector<std::string>& tokens, const std::shared_ptr<LOOLWebSocket>& ws) + void renderTile(const std::vector<std::string>& tokens, const std::shared_ptr<WebSocketHandler>& websocketHandler) { - assert(ws && "Expected a non-null websocket."); - auto tile = TileDesc::parse(tokens); + assert(websocketHandler && "Expected a non-null websocket."); + TileDesc tile = TileDesc::parse(tokens); size_t pixmapDataSize = 4 * tile.getWidth() * tile.getHeight(); std::vector<unsigned char> pixmap; @@ -921,13 +920,13 @@ public: } LOG_TRC("Sending render-tile response (" << output.size() << " bytes) for: " << response); - ws->sendFrame(output.data(), output.size(), WebSocket::FRAME_BINARY); + websocketHandler->sendMessage(output.data(), output.size(), WebSocket::FRAME_BINARY); } - void renderCombinedTiles(const std::vector<std::string>& tokens, const std::shared_ptr<LOOLWebSocket>& ws) + void renderCombinedTiles(const std::vector<std::string>& tokens, const std::shared_ptr<WebSocketHandler>& websocketHandler) { - assert(ws && "Expected a non-null websocket."); - auto tileCombined = TileCombined::parse(tokens); + assert(websocketHandler && "Expected a non-null websocket."); + TileCombined tileCombined = TileCombined::parse(tokens); auto& tiles = tileCombined.getTiles(); Util::Rectangle renderArea; @@ -1050,7 +1049,7 @@ public: std::copy(tileMsg.begin(), tileMsg.end(), response.begin()); std::copy(output.begin(), output.end(), response.begin() + tileMsg.size()); - ws->sendFrame(response.data(), response.size(), WebSocket::FRAME_BINARY); + websocketHandler->sendMessage(response.data(), response.size(), WebSocket::FRAME_BINARY); } bool sendTextFrame(const std::string& message) @@ -1062,13 +1061,13 @@ public: { try { - if (!_ws || _ws->poll(Poco::Timespan(0), Poco::Net::Socket::SelectMode::SELECT_ERROR)) + if (!_websocketHandler) { LOG_ERR("Child Doc: Bad socket while sending [" << getAbbreviatedMessage(buffer, length) << "]."); return false; } - _ws->sendFrame(buffer, length, flags); + _websocketHandler->sendMessage(buffer, length, flags); return true; } catch (const Exception& exc) @@ -1769,11 +1768,11 @@ private: if (tokens[0] == "tile") { - renderTile(tokens, _ws); + renderTile(tokens, _websocketHandler); } else if (tokens[0] == "tilecombine") { - renderCombinedTiles(tokens, _ws); + renderCombinedTiles(tokens, _websocketHandler); } else if (LOOLProtocol::getFirstToken(tokens[0], '-') == "child") { @@ -1896,7 +1895,7 @@ private: std::shared_ptr<lok::Document> _loKitDocument; std::shared_ptr<TileQueue> _tileQueue; - std::shared_ptr<LOOLWebSocket> _ws; + std::shared_ptr<WebSocketHandler> _websocketHandler; PngCache _pngCache; // Document password provided @@ -1932,6 +1931,97 @@ private: Poco::Thread _callbackThread; }; +class KitWebSocketHandler final : public WebSocketHandler, public std::enable_shared_from_this<KitWebSocketHandler> +{ + std::shared_ptr<TileQueue> _queue; + std::string _socketName; + std::shared_ptr<lok::Office> _loKit; + std::string _jailId; + +public: + KitWebSocketHandler(const std::string& socketName, const std::shared_ptr<lok::Office>& loKit, const std::string& jailId) : + WebSocketHandler(/* isClient = */ true), + _queue(std::make_shared<TileQueue>()), + _socketName(socketName), + _loKit(loKit), + _jailId(jailId) + { + } + +protected: + void handleMessage(bool /*fin*/, WSOpCode /*code*/, std::vector<char>& data) override + { + std::string message(data.data(), data.size()); + +#if 0 // FIXME might be needed for unit tests #ifndef KIT_IN_PROCESS + if (UnitKit::get().filterKitMessage(ws, message)) + { + return; + } +#endif + + LOG_DBG(_socketName << ": recv [" << LOOLProtocol::getAbbreviatedMessage(message) << "]."); + std::vector<std::string> tokens = LOOLProtocol::tokenize(message); + + // Note: Syntax or parsing errors here are unexpected and fatal. + if (TerminationFlag) + { + LOG_DBG("Too late, we're going down"); + } + else if (tokens[0] == "session") + { + const std::string& sessionId = tokens[1]; + const std::string& docKey = tokens[2]; + const std::string& docId = tokens[3]; + + std::string url; + URI::decode(docKey, url); + LOG_INF("New session [" << sessionId << "] request on url [" << url << "]."); + + if (!document) + { + document = std::make_shared<Document>(_loKit, _jailId, docKey, docId, url, _queue, shared_from_this()); + } + + // Validate and create session. + if (!(url == document->getUrl() && document->createSession(sessionId))) + { + LOG_DBG("CreateSession failed."); + } + } + else if (tokens[0] == "exit") + { + LOG_TRC("Setting TerminationFlag due to 'exit' command from parent."); + TerminationFlag = true; + } + else if (tokens[0] == "tile" || tokens[0] == "tilecombine" || tokens[0] == "canceltiles" || + tokens[0] == "paintwindow" || + LOOLProtocol::getFirstToken(tokens[0], '-') == "child") + { + if (document) + { + _queue->put(message); + } + else + { + LOG_WRN("No document while processing " << tokens[0] << " request."); + } + } + else if (tokens.size() == 3 && tokens[0] == "setconfig") + { + // Currently onlly rlimit entries are supported. + if (!Rlimit::handleSetrlimitCommand(tokens)) + { + LOG_ERR("Unknown setconfig command: " << message); + } + } + else + { + LOG_ERR("Bad or unknown token [" << tokens[0] << "]"); + } + } +}; + void documentViewCallback(const int type, const char* payload, void* data) { Document::ViewCallback(type, payload, data); @@ -2177,125 +2267,26 @@ void lokit_main(const std::string& childRoot, free(versionInfo); } - // Open websocket connection between the child process and WSD. - HTTPClientSession cs("127.0.0.1", MasterPortNumber); - cs.setTimeout(Poco::Timespan(10, 0)); // 10 second - LOG_DBG("Connecting to Master " << cs.getHost() << ':' << cs.getPort()); - HTTPRequest request(HTTPRequest::HTTP_GET, requestUrl); - HTTPResponse response; - auto ws = std::make_shared<LOOLWebSocket>(cs, request, response); - ws->setReceiveTimeout(0); + SocketPoll mainKit("kit"); - auto queue = std::make_shared<TileQueue>(); + const Poco::URI uri("ws://127.0.0.1"); + uri.setPort(MasterPortNumber); - const std::string socketName = "child_ws_" + pid; - IoUtil::SocketProcessor(ws, socketName, - [&socketName, &ws, &loKit, &jailId, &queue](const std::vector<char>& data) - { - std::string message(data.data(), data.size()); - -#ifndef KIT_IN_PROCESS - if (UnitKit::get().filterKitMessage(ws, message)) - { - return true; - } -#endif - - LOG_DBG(socketName << ": recv [" << LOOLProtocol::getAbbreviatedMessage(message) << "]."); - std::vector<std::string> tokens = LOOLProtocol::tokenize(message); - - // Note: Syntax or parsing errors here are unexpected and fatal. - if (TerminationFlag) - { - LOG_DBG("Too late, we're going down"); - } - else if (tokens[0] == "session") - { - const std::string& sessionId = tokens[1]; - const std::string& docKey = tokens[2]; - const std::string& docId = tokens[3]; - - std::string url; - URI::decode(docKey, url); - LOG_INF("New session [" << sessionId << "] request on url [" << url << "]."); - - if (!document) - { - document = std::make_shared<Document>(loKit, jailId, docKey, docId, url, queue, ws); - } - - // Validate and create session. - if (!(url == document->getUrl() && - document->createSession(sessionId))) - { - LOG_DBG("CreateSession failed."); - } - } - else if (tokens[0] == "exit") - { - LOG_TRC("Setting TerminationFlag due to 'exit' command from parent."); - TerminationFlag = true; - } - else if (tokens[0] == "tile" || tokens[0] == "tilecombine" || tokens[0] == "canceltiles" || - tokens[0] == "paintwindow" || - LOOLProtocol::getFirstToken(tokens[0], '-') == "child") - { - if (document) - { - queue->put(message); - } - else - { - LOG_WRN("No document while processing " << tokens[0] << " request."); - } - } - else if (tokens.size() == 3 && tokens[0] == "setconfig") - { - // Currently onlly rlimit entries are supported. - if (!Rlimit::handleSetrlimitCommand(tokens)) - { - LOG_ERR("Unknown setconfig command: " << message); - } - } - else - { - LOG_ERR("Bad or unknown token [" << tokens[0] << "]"); - } + mainKit.insertNewWebSocketSync(uri, std::make_shared<KitWebSocketHandler>("child_ws_" + pid, loKit, jailId)); + LOG_INF("New kit client websocket inserted."); - return true; - }, - []() {}, - []() - { - if (document && document->purgeSessions() == 0) - { - LOG_INF("Last session discarded. Terminating."); - TerminationFlag = true; - } - - return TerminationFlag.load(); - }); - -#if 0 - std::string uri = "file://$HOME/docs/basic-presentation.pptx"; - std::shared_ptr<lok::Document> loKitDoc; - - const auto flags = LOK_FEATURE_DOCUMENT_PASSWORD - | LOK_FEATURE_DOCUMENT_PASSWORD_TO_MODIFY - | LOK_FEATURE_PART_IN_INVALIDATION_CALLBACK - | LOK_FEATURE_NO_TILED_ANNOTATIONS - | LOK_FEATURE_RANGE_HEADERS; - loKit->setOptionalFeatures(flags); - loKitDoc.reset(loKit->documentLoad(uri.c_str())); - if (!loKitDoc || !loKitDoc->get()) + while (!TerminationFlag) { - LOG_ERR("Failed to load: " << uri << ", error: " << loKit->getError()); - std::_Exit(Application::EXIT_OK); + mainKit.poll(SocketPoll::DefaultPollTimeoutMs); + + if (document && document->purgeSessions() == 0) + { + LOG_INF("Last session discarded. Terminating."); + TerminationFlag = true; + } } - // specific case to debug - // ... -#endif + LOG_INF("Kit poll terminated."); // Let forkit handle the jail cleanup. } commit 43522af092b30d9d7b6267a007772fcb2169dd13 Author: Jan Holesovsky <ke...@collabora.com> Date: Fri May 4 16:08:32 2018 +0200 websocketdump: Read the port and ssl support from the config. Change-Id: Ifc4566d5e1f2cdba1fd4bd7d53b359d81604083b diff --git a/tools/WebSocketDump.cpp b/tools/WebSocketDump.cpp index 7f13012dd..202bff3f7 100644 --- a/tools/WebSocketDump.cpp +++ b/tools/WebSocketDump.cpp @@ -18,6 +18,7 @@ #include <Poco/Net/HTTPRequest.h> #include <Poco/Net/HTTPResponse.h> #include <Poco/StringTokenizer.h> +#include <Poco/Util/XMLConfiguration.h> #include <Log.hpp> #include <Util.hpp> @@ -194,13 +195,19 @@ private: class DumpSocketFactory final : public SocketFactory { +private: + bool _isSSL = false; + +public: + DumpSocketFactory(bool isSSL) : _isSSL(isSSL) {} + std::shared_ptr<Socket> create(const int physicalFd) override { #if ENABLE_SSL - return StreamSocket::create<SslStreamSocket>(physicalFd, false, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); -#else - return StreamSocket::create<StreamSocket>(physicalFd, false, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); + if (_isSSL) + return StreamSocket::create<SslStreamSocket>(physicalFd, false, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); #endif + return StreamSocket::create<StreamSocket>(physicalFd, false, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); } }; @@ -212,9 +219,15 @@ namespace Util } } +class LoolConfig final: public Poco::Util::XMLConfiguration +{ +public: + LoolConfig() + {} +}; + int main (int argc, char **argv) { - int port = 9042; (void) argc; (void) argv; if (!UnitWSD::init(UnitWSD::UnitType::Wsd, "")) @@ -225,6 +238,20 @@ int main (int argc, char **argv) Log::initialize("WebSocketDump", "trace", true, false, std::map<std::string, std::string>()); + LoolConfig config; + config.load("loolwsd.xml"); + + // read the port & ssl support + int port = 9042; + bool isSSL = false; + std::string monitorAddress = config.getString("monitors.monitor"); + if (!monitorAddress.empty()) + { + Poco::URI monitorURI(monitorAddress); + port = monitorURI.getPort(); + isSSL = (monitorURI.getScheme() == "wss"); + } + #if ENABLE_SSL // hard coded but easy for now. const std::string ssl_cert_file_path = "etc/cert.pem"; @@ -233,10 +260,11 @@ int main (int argc, char **argv) const std::string ssl_cipher_list = "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"; // Initialize the non-blocking socket SSL. - SslContext::initialize(ssl_cert_file_path, - ssl_key_file_path, - ssl_ca_file_path, - ssl_cipher_list); + if (isSSL) + SslContext::initialize(ssl_cert_file_path, + ssl_key_file_path, + ssl_ca_file_path, + ssl_cipher_list); #endif SocketPoll acceptPoll("accept"); @@ -244,7 +272,7 @@ int main (int argc, char **argv) // Setup listening socket with a factory for connected sockets. auto serverSocket = std::make_shared<ServerSocket>( Socket::Type::All, DumpSocketPoll, - std::make_shared<DumpSocketFactory>()); + std::make_shared<DumpSocketFactory>(isSSL)); if (!serverSocket->bind(ServerSocket::Type::Public, port)) { commit a88a48b5f3ce2f0ed85f3854933ee1db7d1640b3 Author: Michael Meeks <michael.me...@collabora.com> Date: Thu May 3 18:03:56 2018 +0100 Enable SSL in outbound, client websockets ... Switch SSL context creation to be generic rather than pure server. Change-Id: I1b750b4ddc8c607381f5541a4f4412fa16e457d4 diff --git a/net/Ssl.cpp b/net/Ssl.cpp index a16caa258..0af675688 100644 --- a/net/Ssl.cpp +++ b/net/Ssl.cpp @@ -66,9 +66,9 @@ SslContext::SslContext(const std::string& certFilePath, // Create the Context. We only have one, // as we don't expect/support different servers in same process. #if OPENSSL_VERSION_NUMBER >= 0x10100000L - _ctx = SSL_CTX_new(TLS_server_method()); + _ctx = SSL_CTX_new(TLS_method()); #else - _ctx = SSL_CTX_new(SSLv23_server_method()); + _ctx = SSL_CTX_new(SSLv23_method()); #endif // SSL_CTX_set_default_passwd_cb(_ctx, &privateKeyPassphraseCallback); diff --git a/net/SslSocket.hpp b/net/SslSocket.hpp index c19fedea4..44a2fa382 100644 --- a/net/SslSocket.hpp +++ b/net/SslSocket.hpp @@ -46,7 +46,12 @@ public: SSL_set_bio(_ssl, bio, bio); if (isClient) + { SSL_set_connect_state(_ssl); + if (SSL_connect(_ssl) == 0) + LOG_DBG("SslStreamSocket connect #" << getFD() << " failed "); + // else -1 is quite possibly SSL_ERROR_WANT_READ + } else // We are a server-side socket. SSL_set_accept_state(_ssl); } diff --git a/tools/WebSocketDump.cpp b/tools/WebSocketDump.cpp index 74faa310e..7f13012dd 100644 --- a/tools/WebSocketDump.cpp +++ b/tools/WebSocketDump.cpp @@ -196,7 +196,7 @@ class DumpSocketFactory final : public SocketFactory { std::shared_ptr<Socket> create(const int physicalFd) override { -#if 0 && ENABLE_SSL +#if ENABLE_SSL return StreamSocket::create<SslStreamSocket>(physicalFd, false, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); #else return StreamSocket::create<StreamSocket>(physicalFd, false, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); @@ -225,6 +225,20 @@ int main (int argc, char **argv) Log::initialize("WebSocketDump", "trace", true, false, std::map<std::string, std::string>()); +#if ENABLE_SSL + // hard coded but easy for now. + const std::string ssl_cert_file_path = "etc/cert.pem"; + const std::string ssl_key_file_path = "etc/key.pem"; + const std::string ssl_ca_file_path = "etc/ca-chain.cert.pem"; + const std::string ssl_cipher_list = "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"; + + // Initialize the non-blocking socket SSL. + SslContext::initialize(ssl_cert_file_path, + ssl_key_file_path, + ssl_ca_file_path, + ssl_cipher_list); +#endif + SocketPoll acceptPoll("accept"); // Setup listening socket with a factory for connected sockets. commit 177f253561e594c24f0b50e586f4cfb3d6ce96c8 Author: Michael Meeks <michael.me...@collabora.com> Date: Thu May 3 17:52:35 2018 +0100 re-factor socket factories to take a client parameter. Change-Id: I0be98eb583b4f8081dd8ad23e688e93c55220367 diff --git a/net/Socket.hpp b/net/Socket.hpp index 9c3f7fe48..d0c03071c 100644 --- a/net/Socket.hpp +++ b/net/Socket.hpp @@ -700,7 +700,8 @@ class StreamSocket : public Socket, public std::enable_shared_from_this<StreamSo { public: /// Create a StreamSocket from native FD and take ownership of handler instance. - StreamSocket(const int fd, std::shared_ptr<SocketHandlerInterface> socketHandler) : + StreamSocket(const int fd, bool /* isClient */, + std::shared_ptr<SocketHandlerInterface> socketHandler) : Socket(fd), _socketHandler(std::move(socketHandler)), _bytesSent(0), @@ -827,10 +828,10 @@ public: /// but we can't have a shared_ptr in the ctor. template <typename TSocket> static - std::shared_ptr<TSocket> create(const int fd, std::shared_ptr<SocketHandlerInterface> handler) + std::shared_ptr<TSocket> create(const int fd, bool isClient, std::shared_ptr<SocketHandlerInterface> handler) { SocketHandlerInterface* pHandler = handler.get(); - auto socket = std::make_shared<TSocket>(fd, std::move(handler)); + auto socket = std::make_shared<TSocket>(fd, isClient, std::move(handler)); pHandler->onConnect(socket); return socket; } diff --git a/net/SslSocket.hpp b/net/SslSocket.hpp index 2f8d45cb6..c19fedea4 100644 --- a/net/SslSocket.hpp +++ b/net/SslSocket.hpp @@ -19,8 +19,9 @@ class SslStreamSocket final : public StreamSocket { public: - SslStreamSocket(const int fd, std::shared_ptr<SocketHandlerInterface> responseClient) : - StreamSocket(fd, std::move(responseClient)), + SslStreamSocket(const int fd, bool isClient, + std::shared_ptr<SocketHandlerInterface> responseClient) : + StreamSocket(fd, isClient, std::move(responseClient)), _ssl(nullptr), _sslWantsTo(SslWantsTo::Neither), _doHandshake(true) @@ -44,8 +45,10 @@ public: SSL_set_bio(_ssl, bio, bio); - // We are a server-side socket. - SSL_set_accept_state(_ssl); + if (isClient) + SSL_set_connect_state(_ssl); + else // We are a server-side socket. + SSL_set_accept_state(_ssl); } ~SslStreamSocket() diff --git a/tools/WebSocketDump.cpp b/tools/WebSocketDump.cpp index bc7a04781..74faa310e 100644 --- a/tools/WebSocketDump.cpp +++ b/tools/WebSocketDump.cpp @@ -140,7 +140,10 @@ private: if (request.find("Upgrade") != request.end() && Poco::icompare(request["Upgrade"], "websocket") == 0) { - socket->setHandler(std::make_shared<DumpSocketHandler>(_socket, request)); + auto dumpHandler = std::make_shared<DumpSocketHandler>(_socket, request); + socket->setHandler(dumpHandler); + dumpHandler->sendMessage("version"); + dumpHandler->sendMessage("documents"); } else { @@ -194,9 +197,9 @@ class DumpSocketFactory final : public SocketFactory std::shared_ptr<Socket> create(const int physicalFd) override { #if 0 && ENABLE_SSL - return StreamSocket::create<SslStreamSocket>(physicalFd, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); + return StreamSocket::create<SslStreamSocket>(physicalFd, false, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); #else - return StreamSocket::create<StreamSocket>(physicalFd, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); + return StreamSocket::create<StreamSocket>(physicalFd, false, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); #endif } }; diff --git a/wsd/Admin.cpp b/wsd/Admin.cpp index ce1322877..c34a98aa9 100644 --- a/wsd/Admin.cpp +++ b/wsd/Admin.cpp @@ -701,10 +701,10 @@ void Admin::connectToMonitor(const Poco::URI &uri) std::shared_ptr<SocketHandlerInterface> handler = std::make_shared<MonitorSocketHandler>(this); #if ENABLE_SSL if (isSSL) - socket = StreamSocket::create<SslStreamSocket>(fd, handler); + socket = StreamSocket::create<SslStreamSocket>(fd, true, handler); #endif if (!socket && !isSSL) - socket = StreamSocket::create<StreamSocket>(fd, handler); + socket = StreamSocket::create<StreamSocket>(fd, true, handler); if (socket) { diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp index 86bccc768..cccdf1a71 100644 --- a/wsd/LOOLWSD.cpp +++ b/wsd/LOOLWSD.cpp @@ -2393,7 +2393,7 @@ class PlainSocketFactory final : public SocketFactory std::shared_ptr<Socket> socket = StreamSocket::create<StreamSocket>( - fd, std::unique_ptr<SocketHandlerInterface>{ + fd, false, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); return socket; @@ -2410,7 +2410,9 @@ class SslSocketFactory final : public SocketFactory if (SimulatedLatencyMs > 0) fd = Delay::create(SimulatedLatencyMs, physicalFd); - return StreamSocket::create<SslStreamSocket>(fd, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); + return StreamSocket::create<SslStreamSocket>( + fd, false, std::unique_ptr<SocketHandlerInterface>{ + new ClientRequestDispatcher }); } }; #endif @@ -2420,7 +2422,7 @@ class PrisonerSocketFactory final : public SocketFactory std::shared_ptr<Socket> create(const int fd) override { // No local delay. - return StreamSocket::create<StreamSocket>(fd, std::unique_ptr<SocketHandlerInterface>{ new PrisonerRequestDispatcher }); + return StreamSocket::create<StreamSocket>(fd, false, std::unique_ptr<SocketHandlerInterface>{ new PrisonerRequestDispatcher }); } }; commit 4e7cb3e4a5c3753436b4b02ffd4dfbd2104b1960 Author: Jan Holesovsky <ke...@collabora.com> Date: Thu May 3 17:32:31 2018 +0200 The WebSocketHandler::handleClientUpgrade() needs to handle a Response, not a request. This commit includes some fixes from Michael Meeks too. Change-Id: I25198ded9d354a44d7718071394bcccdcabcdd94 diff --git a/net/Socket.cpp b/net/Socket.cpp index 3380764b5..96ad7f7c4 100644 --- a/net/Socket.cpp +++ b/net/Socket.cpp @@ -297,8 +297,16 @@ bool StreamSocket::parseHeader(const char *clientName, return false; } } + catch (const Poco::Exception& exc) + { + LOG_DBG("parseHeader exception caught: " << exc.displayText()); + // Probably don't have enough data just yet. + // TODO: timeout if we never get enough. + return false; + } catch (const std::exception& exc) { + LOG_DBG("parseHeader exception caught."); // Probably don't have enough data just yet. // TODO: timeout if we never get enough. return false; diff --git a/net/WebSocketHandler.hpp b/net/WebSocketHandler.hpp index 47d917077..c0628b456 100644 --- a/net/WebSocketHandler.hpp +++ b/net/WebSocketHandler.hpp @@ -21,6 +21,7 @@ #include <Poco/MemoryStream.h> #include <Poco/Net/HTTPRequest.h> +#include <Poco/Net/HTTPResponse.h> #include <Poco/Net/WebSocket.h> class WebSocketHandler : public SocketHandlerInterface @@ -494,52 +495,64 @@ protected: { std::shared_ptr<StreamSocket> socket = _socket.lock(); - LOG_TRC("Incoming client websocket upgrade request"); - - Poco::MemoryInputStream message(&socket->_inBuffer[0], - socket->_inBuffer.size());; - Poco::Net::HTTPRequest req; - size_t requestSize = 0; + LOG_TRC("Incoming client websocket upgrade response: " << std::string(&socket->_inBuffer[0], socket->_inBuffer.size())); bool bOk = false; - if (!socket->parseHeader("Monitor", message, req, &requestSize)) - { -// FIXME: grim hack [!] we can't parse the response for some strange reason ... -// we get an exception inside Poco ... -// return; - bOk = true; - } - else if (req.find("Upgrade") != req.end() && Poco::icompare(req["Upgrade"], "websocket") == 0) + size_t responseSize = 0; + + try { - const std::string wsKey = req.get("Sec-WebSocket-Accept", ""); - const std::string wsProtocol = req.get("Sec-WebSocket-Protocol", ""); - if (Poco::icompare(wsProtocol, "chat") != 0) - LOG_ERR("Unknown websocket protocol " << wsProtocol); - else + Poco::MemoryInputStream message(&socket->_inBuffer[0], socket->_inBuffer.size());; + Poco::Net::HTTPResponse response; + + response.read(message); + { - LOG_TRC("Accepted incoming websocket request"); - // FIXME: validate Sec-WebSocket-Accept vs. Sec-WebSocket-Key etc. - bOk = true; + static const std::string marker("\r\n\r\n"); + auto itBody = std::search(socket->_inBuffer.begin(), + socket->_inBuffer.end(), + marker.begin(), marker.end()); + + if (itBody != socket->_inBuffer.end()) + responseSize = itBody - socket->_inBuffer.begin() + marker.size(); } + + if (response.getStatus() == Poco::Net::HTTPResponse::HTTP_SWITCHING_PROTOCOLS && + response.has("Upgrade") && Poco::icompare(response.get("Upgrade"), "websocket") == 0) + { +#if 0 // SAL_DEBUG ... + const std::string wsKey = response.get("Sec-WebSocket-Accept", ""); + const std::string wsProtocol = response.get("Sec-WebSocket-Protocol", ""); + if (Poco::icompare(wsProtocol, "chat") != 0) + LOG_ERR("Unknown websocket protocol " << wsProtocol); + else +#endif + { + LOG_TRC("Accepted incoming websocket response"); + // FIXME: validate Sec-WebSocket-Accept vs. Sec-WebSocket-Key etc. + bOk = true; + } + } + } + catch (const Poco::Exception& exc) + { + LOG_DBG("handleClientUpgrade exception caught: " << exc.displayText()); + } + catch (const std::exception& exc) + { + LOG_DBG("handleClientUpgrade exception caught."); } - if (!bOk) + if (!bOk || responseSize == 0) { - LOG_ERR("Bad websocker server reply: " << req.getURI()); - - // Bad request. - std::ostringstream oss; - oss << "HTTP/1.1 400\r\n" - << "Date: " << Poco::DateTimeFormatter::format(Poco::Timestamp(), Poco::DateTimeFormat::HTTP_FORMAT) << "\r\n" - << "User-Agent: " << WOPI_AGENT_STRING << "\r\n" - << "Content-Length: 0\r\n" - << "\r\n"; - socket->send(oss.str()); + LOG_ERR("Bad websocker server response."); + socket->shutdown(); + return; } setWebSocket(); - socket->eraseFirstInputBytes(requestSize); + socket->eraseFirstInputBytes(responseSize); } void setWebSocket() commit fc59ea70085e770144adf61236ddd6c14941488c Author: Michael Meeks <michael.me...@collabora.com> Date: Tue May 1 14:57:17 2018 +0100 Share HTTP header parsing inside the StreamSocket. Change-Id: Id98e895a939d931ac10b7cd7403da4cbe822ee82 diff --git a/net/Socket.cpp b/net/Socket.cpp index 3098ec1f4..3380764b5 100644 --- a/net/Socket.cpp +++ b/net/Socket.cpp @@ -17,6 +17,8 @@ #include <Poco/DateTime.h> #include <Poco/DateTimeFormat.h> #include <Poco/DateTimeFormatter.h> +#include <Poco/MemoryStream.h> +#include <Poco/Net/HTTPRequest.h> #include <Poco/Net/HTTPResponse.h> #include "SigUtil.hpp" @@ -241,6 +243,70 @@ bool ServerSocket::bind(Type type, int port) return rc == 0; } +bool StreamSocket::parseHeader(const char *clientName, + Poco::MemoryInputStream &message, + Poco::Net::HTTPRequest &request, + size_t *requestSize) +{ + LOG_TRC("#" << getFD() << " handling incoming " << _inBuffer.size() << " bytes."); + + assert(!requestSize || *requestSize == 0); + + // Find the end of the header, if any. + static const std::string marker("\r\n\r\n"); + auto itBody = std::search(_inBuffer.begin(), _inBuffer.end(), + marker.begin(), marker.end()); + if (itBody == _inBuffer.end()) + { + LOG_TRC("#" << getFD() << " doesn't have enough data yet."); + return false; + } + + // Skip the marker. + itBody += marker.size(); + if (requestSize) + *requestSize = static_cast<size_t>(itBody - _inBuffer.begin()); + + try + { + request.read(message); + + Log::StreamLogger logger = Log::info(); + if (logger.enabled()) + { + logger << "#" << getFD() << ": " << clientName << " HTTP Request: " + << request.getMethod() << ' ' + << request.getURI() << ' ' + << request.getVersion(); + + for (const auto& it : request) + { + logger << " / " << it.first << ": " << it.second; + } + + LOG_END(logger); + } + + const std::streamsize contentLength = request.getContentLength(); + const auto offset = itBody - _inBuffer.begin(); + const std::streamsize available = _inBuffer.size() - offset; + + if (contentLength != Poco::Net::HTTPMessage::UNKNOWN_CONTENT_LENGTH && available < contentLength) + { + LOG_DBG("Not enough content yet: ContentLength: " << contentLength << ", available: " << available); + return false; + } + } + catch (const std::exception& exc) + { + // Probably don't have enough data just yet. + // TODO: timeout if we never get enough. + return false; + } + + return true; +} + namespace HttpHelper { diff --git a/net/Socket.hpp b/net/Socket.hpp index beb8b85d3..9c3f7fe48 100644 --- a/net/Socket.hpp +++ b/net/Socket.hpp @@ -40,8 +40,10 @@ namespace Poco { + class MemoryInputStream; namespace Net { + class HTTPRequest; class HTTPResponse; } } @@ -833,6 +835,20 @@ public: return socket; } + /// Remove the first @count bytes from input buffer + void eraseFirstInputBytes(size_t count) + { + _inBuffer.erase(_inBuffer.begin(), _inBuffer.begin() + count); + } + + /// Detects if we have an HTTP header in the provided message and + /// populates a request for that. + bool parseHeader(const char *clientLoggingName, + Poco::MemoryInputStream &message, + Poco::Net::HTTPRequest &request, + size_t *requestSize = nullptr); + + /// Get input/output statistics on this stream void getIOStats(uint64_t &sent, uint64_t &recv) { sent = _bytesSent; @@ -1016,8 +1032,7 @@ namespace HttpHelper void sendFile(const std::shared_ptr<StreamSocket>& socket, const std::string& path, const std::string& mediaType, Poco::Net::HTTPResponse& response, bool noCache = false, bool deflate = false, const bool headerOnly = false); -}; - +} #endif /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp index d329c4e3f..86bccc768 100644 --- a/wsd/LOOLWSD.cpp +++ b/wsd/LOOLWSD.cpp @@ -1600,43 +1600,17 @@ private: return; } - auto socket = _socket.lock(); - std::vector<char>& in = socket->_inBuffer; - - // Find the end of the header, if any. - static const std::string marker("\r\n\r\n"); - auto itBody = std::search(in.begin(), in.end(), - marker.begin(), marker.end()); - if (itBody == in.end()) - { - LOG_TRC("#" << socket->getFD() << " doesn't have enough data yet."); - return; - } - - // Skip the marker. - itBody += marker.size(); + std::shared_ptr<StreamSocket> socket = _socket.lock(); - Poco::MemoryInputStream message(&in[0], in.size()); + Poco::MemoryInputStream message(&socket->_inBuffer[0], + socket->_inBuffer.size());; Poco::Net::HTTPRequest request; + size_t requestSize = 0; + try { - request.read(message); - - auto logger = Log::info(); - if (logger.enabled()) - { - logger << "#" << socket->getFD() << ": Prisoner HTTP Request: " - << request.getMethod() << ' ' - << request.getURI() << ' ' - << request.getVersion(); - - for (const auto& it : request) - { - logger << " / " << it.first << ": " << it.second; - } - - LOG_END(logger); - } + if (!socket->parseHeader("Prisoner", message, request, &requestSize)) + return; LOG_TRC("Child connection with URI [" << request.getURI() << "]."); if (request.getURI().find(NEW_CHILD_URI) != 0) @@ -1677,7 +1651,7 @@ private: return; } - in.clear(); + socket->_inBuffer.clear(); LOG_INF("New child [" << pid << "], jailId: " << jailId << "."); @@ -1760,61 +1734,15 @@ private: /// Called after successful socket reads. void handleIncomingMessage(SocketDisposition &disposition) override { - auto socket = _socket.lock(); - std::vector<char>& in = socket->_inBuffer; - LOG_TRC("#" << socket->getFD() << " handling incoming " << in.size() << " bytes."); - - // Find the end of the header, if any. - static const std::string marker("\r\n\r\n"); - auto itBody = std::search(in.begin(), in.end(), - marker.begin(), marker.end()); - if (itBody == in.end()) - { - LOG_DBG("#" << socket->getFD() << " doesn't have enough data yet."); - return; - } - - // Skip the marker. - itBody += marker.size(); + std::shared_ptr<StreamSocket> socket = _socket.lock(); - Poco::MemoryInputStream message(&in[0], in.size()); + Poco::MemoryInputStream message(&socket->_inBuffer[0], + socket->_inBuffer.size());; Poco::Net::HTTPRequest request; - try - { - request.read(message); - - auto logger = Log::info(); - if (logger.enabled()) - { - logger << "#" << socket->getFD() << ": Client HTTP Request: " - << request.getMethod() << ' ' - << request.getURI() << ' ' - << request.getVersion(); - - for (const auto& it : request) - { - logger << " / " << it.first << ": " << it.second; - } - - LOG_END(logger); - } + size_t requestSize = 0; - const std::streamsize contentLength = request.getContentLength(); - const auto offset = itBody - in.begin(); - const std::streamsize available = in.size() - offset; - - if (contentLength != Poco::Net::HTTPMessage::UNKNOWN_CONTENT_LENGTH && available < contentLength) - { - LOG_DBG("Not enough content yet: ContentLength: " << contentLength << ", available: " << available); - return; - } - } - catch (const std::exception& exc) - { - // Probably don't have enough data just yet. - // TODO: timeout if we never get enough. + if (!socket->parseHeader("Client", message, request, &requestSize)) return; - } try { @@ -1908,12 +1836,12 @@ private: // NOTE: Check _wsState to choose between HTTP response or WebSocket (app-level) error. LOG_INF("#" << socket->getFD() << " Exception while processing incoming request: [" << - LOOLProtocol::getAbbreviatedMessage(in) << "]: " << exc.what()); + LOOLProtocol::getAbbreviatedMessage(socket->_inBuffer) << "]: " << exc.what()); } // if we succeeded - remove the request from our input buffer // we expect one request per socket - in.erase(in.begin(), itBody); + socket->eraseFirstInputBytes(requestSize); } int getPollEvents(std::chrono::steady_clock::time_point /* now */, commit 5e079e160a0667e14c47707e3d385fd8b1c5c000 Author: Michael Meeks <michael.me...@collabora.com> Date: Wed May 2 15:40:16 2018 +0100 Get ping/pong handling sorted with more isClient conditionality. Change-Id: I859ed5b5bcc302304e23ad3554247af920de2421 diff --git a/net/WebSocketHandler.hpp b/net/WebSocketHandler.hpp index 5663cab2c..47d917077 100644 --- a/net/WebSocketHandler.hpp +++ b/net/WebSocketHandler.hpp @@ -197,24 +197,51 @@ public: ", fin? " << fin << ", mask? " << hasMask << ", payload length: " << _wsPayload.size() << ", residual socket data: " << socket->_inBuffer.size() << " bytes."); + bool doClose = false; + switch (code) { case WSOpCode::Pong: { - _pingTimeUs = std::chrono::duration_cast<std::chrono::microseconds> - (std::chrono::steady_clock::now() - _lastPingSentTime).count(); - LOG_TRC("#" << socket->getFD() << ": Pong received: " << _pingTimeUs << " microseconds"); - break; + if (_isClient) + { + LOG_ERR("#" << socket->getFD() << ": Servers should not send pongs, only clients"); + doClose = true; + break; + } + else + { + _pingTimeUs = std::chrono::duration_cast<std::chrono::microseconds> + (std::chrono::steady_clock::now() - _lastPingSentTime).count(); + LOG_TRC("#" << socket->getFD() << ": Pong received: " << _pingTimeUs << " microseconds"); + break; + } } case WSOpCode::Ping: - LOG_ERR("#" << socket->getFD() << ": Clients should not send pings, only servers"); - // drop through -#if defined __clang__ - [[clang::fallthrough]]; -#elif defined __GNUC__ && __GNUC__ >= 7 - [[fallthrough]]; -#endif + if (_isClient) + { + auto now = std::chrono::steady_clock::now(); + _pingTimeUs = std::chrono::duration_cast<std::chrono::microseconds> + (now - _lastPingSentTime).count(); + sendPong(now, &_wsPayload[0], payloadLen, socket); + break; + } + else + { + LOG_ERR("#" << socket->getFD() << ": Clients should not send pings, only servers"); + doClose = true; + } + break; case WSOpCode::Close: + doClose = true; + break; + default: + handleMessage(fin, code, _wsPayload); + break; + } + + if (doClose) + { if (!_shuttingDown) { // Peer-initiated shutdown must be echoed. @@ -239,10 +266,6 @@ public: // TCP Close. socket->closeConnection(); - break; - default: - handleMessage(fin, code, _wsPayload); - break; } _wsPayload.clear(); @@ -270,15 +293,20 @@ public: int getPollEvents(std::chrono::steady_clock::time_point now, int & timeoutMaxMs) override { - const int timeSincePingMs = - std::chrono::duration_cast<std::chrono::milliseconds>(now - _lastPingSentTime).count(); - timeoutMaxMs = std::min(timeoutMaxMs, PingFrequencyMs - timeSincePingMs); + if (!_isClient) + { + const int timeSincePingMs = + std::chrono::duration_cast<std::chrono::milliseconds>(now - _lastPingSentTime).count(); + timeoutMaxMs = std::min(timeoutMaxMs, PingFrequencyMs - timeSincePingMs); + } return POLLIN; } /// Send a ping message - void sendPing(std::chrono::steady_clock::time_point now, - const std::shared_ptr<StreamSocket>& socket) + void sendPingOrPong(std::chrono::steady_clock::time_point now, + const char* data, const size_t len, + const WSOpCode code, + const std::shared_ptr<StreamSocket>& socket) { assert(socket && "Expected a valid socket instance."); @@ -290,15 +318,34 @@ public: return; } - LOG_TRC("#" << socket->getFD() << ": Sending ping."); + LOG_TRC("#" << socket->getFD() << ": Sending " << + (const char *)(code == WSOpCode::Ping ? " ping." : "pong.")); // FIXME: allow an empty payload. - sendMessage("", 1, WSOpCode::Ping, false); + sendMessage(data, len, code, false); _lastPingSentTime = now; } + void sendPing(std::chrono::steady_clock::time_point now, + const std::shared_ptr<StreamSocket>& socket) + { + assert(!_isClient); + sendPingOrPong(now, "", 1, WSOpCode::Ping, socket); + } + + void sendPong(std::chrono::steady_clock::time_point now, + const char* data, const size_t len, + const std::shared_ptr<StreamSocket>& socket) + { + assert(_isClient); + sendPingOrPong(now, data, len, WSOpCode::Pong, socket); + } + /// Do we need to handle a timeout ? void checkTimeout(std::chrono::steady_clock::time_point now) override { + if (_isClient) + return; + const int timeSincePingMs = std::chrono::duration_cast<std::chrono::milliseconds>(now - _lastPingSentTime).count(); if (timeSincePingMs >= PingFrequencyMs) commit a631f129ba5918a11488808dd90dd30f508a7bcf Author: Michael Meeks <michael.me...@collabora.com> Date: Tue May 1 17:50:13 2018 +0100 More work on client / Monitor websocket connections. Change-Id: Ic70fe522e24f2b1863c2d9d1dd6941785510758a diff --git a/net/WebSocketHandler.hpp b/net/WebSocketHandler.hpp index dd5c4be08..5663cab2c 100644 --- a/net/WebSocketHandler.hpp +++ b/net/WebSocketHandler.hpp @@ -19,6 +19,7 @@ #include "common/Unit.hpp" #include "Socket.hpp" +#include <Poco/MemoryStream.h> #include <Poco/Net/HTTPRequest.h> #include <Poco/Net/WebSocket.h> @@ -33,6 +34,7 @@ protected: std::vector<char> _wsPayload; std::atomic<bool> _shuttingDown; + bool _isClient; struct WSFrameMask { @@ -44,10 +46,12 @@ protected: static const int PingFrequencyMs; public: - WebSocketHandler() : + /// Perform upgrade ourselves, or select a client web socket. + WebSocketHandler(bool isClient = false) : _lastPingSentTime(std::chrono::steady_clock::now()), _pingTimeUs(0), - _shuttingDown(false) + _shuttingDown(false), + _isClient(isClient) { } @@ -59,7 +63,8 @@ public: std::chrono::milliseconds(PingFrequencyMs) - std::chrono::milliseconds(InitialPingDelayMs)), _pingTimeUs(0), - _shuttingDown(false) + _shuttingDown(false), + _isClient(false) { upgradeToWebSocket(request); } @@ -253,6 +258,8 @@ public: { LOG_ERR("No socket associated with WebSocketHandler 0x" << std::hex << this << std::dec); } ... etc. - the rest is truncated _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits