Rebased ref, commits from common ancestor: commit 634e04aeebcaaf4fe61a684a05e0f3f682234e22 Author: Michael Meeks <michael.me...@collabora.com> AuthorDate: Sat Mar 21 15:07:10 2020 +0000 Commit: Michael Meeks <michael.me...@collabora.com> CommitDate: Sat Mar 21 15:07:10 2020 +0000
Proxy: improve debugging & naming. Change-Id: Ifba669a33855a67c9a4e968db42ef1a2cb301d63 diff --git a/loleaflet/js/global.js b/loleaflet/js/global.js index 95ff625f7..9f319215f 100644 --- a/loleaflet/js/global.js +++ b/loleaflet/js/global.js @@ -197,7 +197,7 @@ }; this.parseIncomingArray = function(arr) { var decoder = new TextDecoder(); - console.debug('Parse incoming array of length ' + arr.length); + console.debug('proxy: parse incoming array of length ' + arr.length); for (var i = 0; i < arr.length; ++i) { var left = arr.length - i; @@ -255,7 +255,7 @@ if (this.status == 200) that.parseIncomingArray(new Uint8Array(this.response)); else - console.debug('Error on incoming response'); + console.debug('proxy: error on incoming response'); }); } req.send(that.sendQueue); @@ -281,21 +281,24 @@ this.sendTimeout = setTimeout(this.doSend, 2 /* ms */); }; this.close = function() { - console.debug('close socket'); + console.debug('proxy: close socket'); this.readyState = 3; this.onclose(); + clearInterval(this.waitInterval); + this.waitInterval = undefined; }; this.getEndPoint = function(type) { var base = this.uri; return base.replace(/^ws/, 'http') + '/' + type; }; - console.debug('New proxy socket ' + this.id + ' ' + this.uri); + console.debug('proxy: new socket ' + this.id + ' ' + this.uri); // queue fetch of session id. this.getSessionId(); // horrors ... - this.readInterval = setInterval(function() { + this.waitConnect = function() { + console.debug('proxy: waiting - ' + that.readWaiting + ' on session ' + that.sessionId); if (that.readWaiting > 4) // max 4 waiting connections concurrently. return; if (that.sessionId == 'fetchsession') @@ -310,13 +313,16 @@ }); req.addEventListener('loadend', function() { that.readWaiting--; + console.debug('proxy: wait ended, re-issue'); + that.waitConnect(); }); - req.open('GET', that.getEndPoint('read')); + req.open('GET', that.getEndPoint('wait')); req.setRequestHeader('SessionId', that.sessionId); req.responseType = 'arraybuffer'; req.send(''); that.readWaiting++; - }, 250); + }; + this.waitInterval = setInterval(this.waitConnect, 250); }; if (global.socketProxy) diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp index 84db6ac84..05c8c1e9d 100644 --- a/wsd/LOOLWSD.cpp +++ b/wsd/LOOLWSD.cpp @@ -2833,7 +2833,7 @@ private: none, url, docKey, _id, uriPublic); std::string fullURL = request.getURI(); - std::string ending = "/ws/read"; + std::string ending = "/ws/wait"; bool isWaiting = (fullURL.size() > ending.size() && std::equal(ending.rbegin(), ending.rend(), fullURL.rbegin())); if (docBroker) diff --git a/wsd/ProxyProtocol.cpp b/wsd/ProxyProtocol.cpp index 0928b4541..19dd70692 100644 --- a/wsd/ProxyProtocol.cpp +++ b/wsd/ProxyProtocol.cpp @@ -31,7 +31,7 @@ void DocumentBroker::handleProxyRequest( std::shared_ptr<ClientSession> clientSession; if (sessionId == "fetchsession") { - LOG_TRC("Create session for " << _docKey); + LOG_TRC("proxy: Create session for " << _docKey); clientSession = createNewClientSession( std::make_shared<ProxyProtocolHandler>(), id, uriPublic, isReadOnly, hostNoTrust); @@ -39,7 +39,7 @@ void DocumentBroker::handleProxyRequest( LOOLWSD::checkDiskSpaceAndWarnClients(true); LOOLWSD::checkSessionLimitsAndWarnClients(); - LOG_TRC("Returning id " << clientSession->getId()); + LOG_TRC("proxy: Returning sessionId " << clientSession->getId()); std::ostringstream oss; oss << "HTTP/1.1 200 OK\r\n" @@ -57,7 +57,7 @@ void DocumentBroker::handleProxyRequest( } else { - LOG_TRC("Find session for " << _docKey << " with id " << sessionId); + LOG_TRC("proxy: find session for " << _docKey << " with id " << sessionId); for (const auto &it : _sessions) { if (it.second->getId() == sessionId) @@ -133,28 +133,29 @@ void ProxyProtocolHandler::handleRequest(bool isWaiting, const std::shared_ptr<S { auto streamSocket = std::static_pointer_cast<StreamSocket>(socket); - LOG_INF("Proxy handle request type: " << (isWaiting ? "wait" : "respond")); + LOG_INF("proxy: handle request type: " << (isWaiting ? "wait" : "respond") << + " on socket [" << socket->getFD() << "]"); if (!isWaiting) { if (!_msgHandler) - LOG_WRN("unusual - incoming message with no-one to handle it"); + LOG_WRN("proxy: unusual - incoming message with no-one to handle it"); else if (!parseEmitIncoming(streamSocket)) { std::stringstream oss; streamSocket->dumpState(oss); - LOG_ERR("bad socket structure " << oss.str()); + LOG_ERR("proxy: bad socket structure " << oss.str()); } } if (!flushQueueTo(streamSocket) && isWaiting) { - LOG_TRC("Queue a waiting socket"); + LOG_TRC("proxy: queue a waiting out socket [" << streamSocket->getFD()); // longer running 'write socket' (marked 'read' by the client) _outSockets.push_back(streamSocket); if (_outSockets.size() > 16) { - LOG_ERR("Unexpected - client opening many concurrent waiting connections " << _outSockets.size()); + LOG_ERR("proxy: Unexpected - client opening many concurrent waiting connections " << _outSockets.size()); // cleanup older waiting sockets. auto sockWeak = _outSockets.front(); _outSockets.erase(_outSockets.begin()); @@ -180,7 +181,7 @@ void ProxyProtocolHandler::handleIncomingMessage(SocketDisposition &disposition) int ProxyProtocolHandler::sendMessage(const char *msg, const size_t len, bool text, bool flush) { _writeQueue.push_back(std::make_shared<Message>(msg, len, text)); - auto sock = popWriteSocket(); + auto sock = popOutSocket(); if (sock && flush) { flushQueueTo(sock); @@ -230,16 +231,24 @@ int ProxyProtocolHandler::getPollEvents(std::chrono::steady_clock::time_point /* return events; } -void ProxyProtocolHandler::performWrites() +/// slurp from the core to us, @returns true if there are messages to send +bool ProxyProtocolHandler::slurpHasMessages() { - if (_msgHandler) + if (_msgHandler && _msgHandler->hasQueuedMessages()) _msgHandler->writeQueuedMessages(); - if (_writeQueue.size() <= 0) + + return _writeQueue.size() > 0; +} + +void ProxyProtocolHandler::performWrites() +{ + if (!slurpHasMessages()) return; - auto sock = popWriteSocket(); + auto sock = popOutSocket(); if (sock) { + LOG_TRC("proxy: performWrites"); flushQueueTo(sock); sock->shutdown(); } @@ -247,9 +256,8 @@ void ProxyProtocolHandler::performWrites() bool ProxyProtocolHandler::flushQueueTo(const std::shared_ptr<StreamSocket> &socket) { - // slurp from the core to us. - if (_msgHandler && _msgHandler->hasQueuedMessages()) - _msgHandler->writeQueuedMessages(); + if (!slurpHasMessages()) + return false; size_t totalSize = 0; for (auto it : _writeQueue) @@ -258,6 +266,8 @@ bool ProxyProtocolHandler::flushQueueTo(const std::shared_ptr<StreamSocket> &soc if (!totalSize) return false; + LOG_TRC("proxy: flushQueue of size " << totalSize << " to socket [" << socket->getFD() << " & close"); + std::ostringstream oss; oss << "HTTP/1.1 200 OK\r\n" "Last-Modified: " << Util::getHttpTimeNow() << "\r\n" @@ -276,7 +286,7 @@ bool ProxyProtocolHandler::flushQueueTo(const std::shared_ptr<StreamSocket> &soc } // LRU-ness ... -std::shared_ptr<StreamSocket> ProxyProtocolHandler::popWriteSocket() +std::shared_ptr<StreamSocket> ProxyProtocolHandler::popOutSocket() { std::weak_ptr<StreamSocket> sock; while (!_outSockets.empty()) @@ -285,8 +295,12 @@ std::shared_ptr<StreamSocket> ProxyProtocolHandler::popWriteSocket() _outSockets.erase(_outSockets.begin()); auto realSock = sock.lock(); if (realSock) + { + LOG_TRC("proxy: popped an out socket [" << realSock->getFD() << " leaving: " << _outSockets.size()); return realSock; + } } + LOG_TRC("proxy: no out sockets to pop."); return std::shared_ptr<StreamSocket>(); } diff --git a/wsd/ProxyProtocol.hpp b/wsd/ProxyProtocol.hpp index 61f0f32be..22cd48fb2 100644 --- a/wsd/ProxyProtocol.hpp +++ b/wsd/ProxyProtocol.hpp @@ -61,7 +61,8 @@ public: void handleRequest(bool isWaiting, const std::shared_ptr<Socket> &socket); private: - std::shared_ptr<StreamSocket> popWriteSocket(); + std::shared_ptr<StreamSocket> popOutSocket(); + bool slurpHasMessages(); int sendMessage(const char *msg, const size_t len, bool text, bool flush); bool flushQueueTo(const std::shared_ptr<StreamSocket> &socket); commit 3b2ec64f4d1fe395f37623d889337bf4caca9f2c Author: Michael Meeks <michael.me...@collabora.com> AuthorDate: Sat Mar 21 14:27:15 2020 +0000 Commit: Michael Meeks <michael.me...@collabora.com> CommitDate: Sat Mar 21 14:27:15 2020 +0000 Proxy: ensure dumpState dumps via the MessageHandlerInterface too. Change-Id: If514e2359188d56bbf7ddef6e04f9d8bf5c50910 diff --git a/net/Socket.cpp b/net/Socket.cpp index 5bb1fa250..675ee64c6 100644 --- a/net/Socket.cpp +++ b/net/Socket.cpp @@ -394,6 +394,8 @@ void WebSocketHandler::dumpState(std::ostream& os) if (_wsPayload.size() > 0) Util::dumpHex(os, "\t\tws queued payload:\n", "\t\t", _wsPayload); os << "\n"; + if (_msgHandler) + _msgHandler->dumpState(os); } void StreamSocket::dumpState(std::ostream& os) diff --git a/wsd/ProxyProtocol.cpp b/wsd/ProxyProtocol.cpp index 7db033c99..0928b4541 100644 --- a/wsd/ProxyProtocol.cpp +++ b/wsd/ProxyProtocol.cpp @@ -217,6 +217,8 @@ void ProxyProtocolHandler::dumpState(std::ostream& os) os << "proxy protocol sockets: " << _outSockets.size() << " writeQueue: " << _writeQueue.size() << ":\n"; for (auto it : _writeQueue) Util::dumpHex(os, "\twrite queue entry:", "\t\t", *it); + if (_msgHandler) + _msgHandler->dumpState(os); } int ProxyProtocolHandler::getPollEvents(std::chrono::steady_clock::time_point /* now */, commit 506d7f0e8e684530c16da7699a746fc590097e44 Author: Michael Meeks <michael.me...@collabora.com> AuthorDate: Sat Mar 21 14:19:49 2020 +0000 Commit: Michael Meeks <michael.me...@collabora.com> CommitDate: Sat Mar 21 14:19:49 2020 +0000 Proxy: make eslint happier. Change-Id: I9ecec787a9a69633a015459eaf39d4b8bd5bb61d diff --git a/loleaflet/js/global.js b/loleaflet/js/global.js index 98c1d1afe..95ff625f7 100644 --- a/loleaflet/js/global.js +++ b/loleaflet/js/global.js @@ -1,4 +1,5 @@ /* -*- js-indent-level: 8 -*- */ +/* global Uint8Array */ (function (global) { var ua = navigator.userAgent.toLowerCase(), @@ -293,8 +294,6 @@ // queue fetch of session id. this.getSessionId(); - var that = this; - // horrors ... this.readInterval = setInterval(function() { if (that.readWaiting > 4) // max 4 waiting connections concurrently. commit 7975d973749d11976d27eb4b4473f37e6109e632 Author: Michael Meeks <michael.me...@collabora.com> AuthorDate: Fri Mar 20 20:45:38 2020 +0000 Commit: Michael Meeks <michael.me...@collabora.com> CommitDate: Sat Mar 21 14:07:24 2020 +0000 Proxy: poll for output space if we need waking. Change-Id: I18a5e71bd3342eea7992672d9be1f5518ea008e3 diff --git a/loleaflet/js/global.js b/loleaflet/js/global.js index 74d72af98..98c1d1afe 100644 --- a/loleaflet/js/global.js +++ b/loleaflet/js/global.js @@ -196,6 +196,7 @@ }; this.parseIncomingArray = function(arr) { var decoder = new TextDecoder(); + console.debug('Parse incoming array of length ' + arr.length); for (var i = 0; i < arr.length; ++i) { var left = arr.length - i; diff --git a/wsd/ProxyProtocol.cpp b/wsd/ProxyProtocol.cpp index 25602f146..7db033c99 100644 --- a/wsd/ProxyProtocol.cpp +++ b/wsd/ProxyProtocol.cpp @@ -219,6 +219,15 @@ void ProxyProtocolHandler::dumpState(std::ostream& os) Util::dumpHex(os, "\twrite queue entry:", "\t\t", *it); } +int ProxyProtocolHandler::getPollEvents(std::chrono::steady_clock::time_point /* now */, + int &/* timeoutMaxMs */) +{ + int events = POLLIN; + if (_msgHandler && _msgHandler->hasQueuedMessages()) + events |= POLLOUT; + return events; +} + void ProxyProtocolHandler::performWrites() { if (_msgHandler) diff --git a/wsd/ProxyProtocol.hpp b/wsd/ProxyProtocol.hpp index ca7070b27..61f0f32be 100644 --- a/wsd/ProxyProtocol.hpp +++ b/wsd/ProxyProtocol.hpp @@ -35,11 +35,7 @@ public: void handleIncomingMessage(SocketDisposition &/* disposition */) override; int getPollEvents(std::chrono::steady_clock::time_point /* now */, - int &/* timeoutMaxMs */) override - { - // underlying buffer based polling is fine. - return POLLIN; - } + int &/* timeoutMaxMs */) override; void checkTimeout(std::chrono::steady_clock::time_point /* now */) override { commit e3f6faff933f410b1cabc72775f4120256ea159e Author: Michael Meeks <michael.me...@collabora.com> AuthorDate: Fri Mar 20 20:15:08 2020 +0000 Commit: Michael Meeks <michael.me...@collabora.com> CommitDate: Sat Mar 21 14:07:24 2020 +0000 Proxy: open four wait sockets concurrently. Change-Id: I08b85677be528b7aa77272a8527c9bacf3f7c336 diff --git a/loleaflet/js/global.js b/loleaflet/js/global.js index 4f68e841b..74d72af98 100644 --- a/loleaflet/js/global.js +++ b/loleaflet/js/global.js @@ -187,7 +187,7 @@ this.sessionId = 'fetchsession'; this.id = window.proxySocketCounter++; this.sendCounter = 0; - this.readWaiting = false; + this.readWaiting = 0; this.onclose = function() { }; this.onerror = function() { @@ -296,9 +296,9 @@ // horrors ... this.readInterval = setInterval(function() { - if (this.readWaiting) // one at a time for now + if (that.readWaiting > 4) // max 4 waiting connections concurrently. return; - if (this.sessionId == 'fetchsession') + if (that.sessionId == 'fetchsession') return; // waiting for our session id. var req = new XMLHttpRequest(); // fetch session id: @@ -307,13 +307,15 @@ that.parseIncomingArray(new Uint8Array(this.response)); else console.debug('Handle error ' + this.status); - that.readWaiting = false; + }); + req.addEventListener('loadend', function() { + that.readWaiting--; }); req.open('GET', that.getEndPoint('read')); req.setRequestHeader('SessionId', that.sessionId); req.responseType = 'arraybuffer'; req.send(''); - that.readWaiting = true; + that.readWaiting++; }, 250); }; diff --git a/wsd/DocumentBroker.hpp b/wsd/DocumentBroker.hpp index cb5bf54e3..6bcb606f3 100644 --- a/wsd/DocumentBroker.hpp +++ b/wsd/DocumentBroker.hpp @@ -258,7 +258,8 @@ public: const Poco::URI& uriPublic, const bool isReadOnly, const std::string& hostNoTrust, - const std::shared_ptr<StreamSocket> &socket); + const std::shared_ptr<StreamSocket> &socket, + bool isWaiting); /// Thread safe termination of this broker if it has a lingering thread void joinThread(); diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp index 514060624..84db6ac84 100644 --- a/wsd/LOOLWSD.cpp +++ b/wsd/LOOLWSD.cpp @@ -2831,11 +2831,16 @@ private: // Request a kit process for this doc. std::shared_ptr<DocumentBroker> docBroker = findOrCreateDocBroker( none, url, docKey, _id, uriPublic); + + std::string fullURL = request.getURI(); + std::string ending = "/ws/read"; + bool isWaiting = (fullURL.size() > ending.size() && + std::equal(ending.rbegin(), ending.rend(), fullURL.rbegin())); if (docBroker) { // need to move into the DocumentBroker context before doing session lookup / creation etc. std::string id = _id; - disposition.setMove([docBroker, id, uriPublic, isReadOnly, hostNoTrust, sessionId] + disposition.setMove([docBroker, id, uriPublic, isReadOnly, hostNoTrust, sessionId, isWaiting] (const std::shared_ptr<Socket> &moveSocket) { LOG_TRC("Setting up docbroker thread for " << docBroker->getDocKey()); @@ -2845,7 +2850,8 @@ private: // We no longer own this socket. moveSocket->setThreadOwner(std::thread::id()); - docBroker->addCallback([docBroker, id, uriPublic, isReadOnly, hostNoTrust, sessionId, moveSocket]() + docBroker->addCallback([docBroker, id, uriPublic, isReadOnly, hostNoTrust, + sessionId, moveSocket, isWaiting]() { // Now inside the document broker thread ... LOG_TRC("In the docbroker thread for " << docBroker->getDocKey()); @@ -2855,7 +2861,7 @@ private: { docBroker->handleProxyRequest( sessionId, id, uriPublic, isReadOnly, - hostNoTrust, streamSocket); + hostNoTrust, streamSocket, isWaiting); return; } catch (const UnauthorizedRequestException& exc) diff --git a/wsd/ProxyProtocol.cpp b/wsd/ProxyProtocol.cpp index 8aaff0131..25602f146 100644 --- a/wsd/ProxyProtocol.cpp +++ b/wsd/ProxyProtocol.cpp @@ -25,7 +25,8 @@ void DocumentBroker::handleProxyRequest( const Poco::URI& uriPublic, const bool isReadOnly, const std::string& hostNoTrust, - const std::shared_ptr<StreamSocket> &socket) + const std::shared_ptr<StreamSocket> &socket, + bool isWaiting) { std::shared_ptr<ClientSession> clientSession; if (sessionId == "fetchsession") @@ -82,7 +83,7 @@ void DocumentBroker::handleProxyRequest( auto proxy = std::static_pointer_cast<ProxyProtocolHandler>( protocol); - proxy->handleRequest(uriPublic.toString(), socket); + proxy->handleRequest(isWaiting, socket); } bool ProxyProtocolHandler::parseEmitIncoming( @@ -128,16 +129,13 @@ bool ProxyProtocolHandler::parseEmitIncoming( return true; } -void ProxyProtocolHandler::handleRequest(const std::string &uriPublic, - const std::shared_ptr<Socket> &socket) +void ProxyProtocolHandler::handleRequest(bool isWaiting, const std::shared_ptr<Socket> &socket) { auto streamSocket = std::static_pointer_cast<StreamSocket>(socket); - bool bRead = uriPublic.find("/write") == std::string::npos; - LOG_INF("Proxy handle request " << uriPublic << " type: " << - (bRead ? "read" : "write")); + LOG_INF("Proxy handle request type: " << (isWaiting ? "wait" : "respond")); - if (bRead) + if (!isWaiting) { if (!_msgHandler) LOG_WRN("unusual - incoming message with no-one to handle it"); @@ -149,13 +147,27 @@ void ProxyProtocolHandler::handleRequest(const std::string &uriPublic, } } - if (!flushQueueTo(streamSocket) && !bRead) + if (!flushQueueTo(streamSocket) && isWaiting) { - // longer running 'write socket' - _writeSockets.push_back(streamSocket); + LOG_TRC("Queue a waiting socket"); + // longer running 'write socket' (marked 'read' by the client) + _outSockets.push_back(streamSocket); + if (_outSockets.size() > 16) + { + LOG_ERR("Unexpected - client opening many concurrent waiting connections " << _outSockets.size()); + // cleanup older waiting sockets. + auto sockWeak = _outSockets.front(); + _outSockets.erase(_outSockets.begin()); + auto sock = sockWeak.lock(); + if (sock) + sock->shutdown(); + } } else + { + LOG_TRC("Return a reply immediately"); socket->shutdown(); + } } void ProxyProtocolHandler::handleIncomingMessage(SocketDisposition &disposition) @@ -202,7 +214,7 @@ void ProxyProtocolHandler::getIOStats(uint64_t &sent, uint64_t &recv) void ProxyProtocolHandler::dumpState(std::ostream& os) { - os << "proxy protocol sockets: " << _writeSockets.size() << " writeQueue: " << _writeQueue.size() << ":\n"; + os << "proxy protocol sockets: " << _outSockets.size() << " writeQueue: " << _writeQueue.size() << ":\n"; for (auto it : _writeQueue) Util::dumpHex(os, "\twrite queue entry:", "\t\t", *it); } @@ -256,10 +268,10 @@ bool ProxyProtocolHandler::flushQueueTo(const std::shared_ptr<StreamSocket> &soc std::shared_ptr<StreamSocket> ProxyProtocolHandler::popWriteSocket() { std::weak_ptr<StreamSocket> sock; - while (!_writeSockets.empty()) + while (!_outSockets.empty()) { - sock = _writeSockets.front(); - _writeSockets.erase(_writeSockets.begin()); + sock = _outSockets.front(); + _outSockets.erase(_outSockets.begin()); auto realSock = sock.lock(); if (realSock) return realSock; diff --git a/wsd/ProxyProtocol.hpp b/wsd/ProxyProtocol.hpp index 091ac3295..ca7070b27 100644 --- a/wsd/ProxyProtocol.hpp +++ b/wsd/ProxyProtocol.hpp @@ -61,11 +61,8 @@ public: void shutdown(bool goingAway = false, const std::string &statusMessage = "") override; void getIOStats(uint64_t &sent, uint64_t &recv) override; void dumpState(std::ostream& os); - bool parseEmitIncoming(const std::shared_ptr<StreamSocket> &socket); - - void handleRequest(const std::string &uriPublic, - const std::shared_ptr<Socket> &socket); + void handleRequest(bool isWaiting, const std::shared_ptr<Socket> &socket); private: std::shared_ptr<StreamSocket> popWriteSocket(); @@ -89,7 +86,7 @@ private: }; /// queue things when we have no socket to hand. std::vector<std::shared_ptr<Message>> _writeQueue; - std::vector<std::weak_ptr<StreamSocket>> _writeSockets; + std::vector<std::weak_ptr<StreamSocket>> _outSockets; }; #endif commit ea277d331d67834c8e0f37ef2e25ec620c6e3967 Author: Michael Meeks <michael.me...@collabora.com> AuthorDate: Fri Mar 20 19:05:48 2020 +0000 Commit: Michael Meeks <michael.me...@collabora.com> CommitDate: Sat Mar 21 14:07:24 2020 +0000 Proxy: re-write css image URLs to handle the proxy. Change-Id: I09f3dea2f5e3a51869d5b0aa3f473d8f3ba75f44 diff --git a/loleaflet/js/global.js b/loleaflet/js/global.js index 78bceaa9c..4f68e841b 100644 --- a/loleaflet/js/global.js +++ b/loleaflet/js/global.js @@ -317,6 +317,33 @@ }, 250); }; + if (global.socketProxy) + { + // re-write relative URLs in CSS - somewhat grim. + window.addEventListener('load', function() { + var sheets = document.styleSheets; + for (var i = 0; i < sheets.length; ++i) { + var relBases = sheets[i].href.split('/'); + relBases.pop(); // bin last - css name. + var replaceBase = 'url("' + relBases.join('/') + '/images/'; + + var rules = sheets[i].cssRules || sheets[i].rules; + for (var r = 0; r < rules.length; ++r) { + if (!rules[r] || !rules[r].style) + continue; + var img = rules[r].style.backgroundImage; + if (img === '' || img === undefined) + continue; + if (img.startsWith('url("images/')) + { + rules[r].style.backgroundImage = + img.replace('url("images/', replaceBase); + } + } + } + }, false); + } + global.createWebSocket = function(uri) { if (global.socketProxy) { return new global.ProxySocket(uri); commit 0106141140cf987bc4ca977b2cc106bbbc41f8b1 Author: Michael Meeks <michael.me...@collabora.com> AuthorDate: Fri Mar 20 16:38:14 2020 +0000 Commit: Michael Meeks <michael.me...@collabora.com> CommitDate: Sat Mar 21 14:07:24 2020 +0000 Proxy: send multiple messages in a single request. Change-Id: Ic0a303979478801bd23941e8893ce5721cf3e732 diff --git a/loleaflet/js/global.js b/loleaflet/js/global.js index c1edc9c7f..78bceaa9c 100644 --- a/loleaflet/js/global.js +++ b/loleaflet/js/global.js @@ -176,6 +176,7 @@ global.proxySocketCounter = 0; global.ProxySocket = function (uri) { + var that = this; this.uri = uri; this.binaryType = 'arraybuffer'; this.bufferedAmount = 0; @@ -235,56 +236,48 @@ i += size; // skip trailing '\n' in loop-increment } }; - this.parseIncoming = function(type, msg) { - if (type === 'blob') - { - var fileReader = new FileReader(); - var that = this; - fileReader.onload = function(event) { - that.parseIncomingArray(event.target.result); - }; - fileReader.readAsArrayBuffer(msg); - } - else if (type === 'arraybuffer') - { - this.parseIncomingArray(new Uint8Array(msg)); - } - else if (type === 'text' || type === '') - { - const encoder = new TextEncoder() - const arr = encoder.encode(msg); - this.parseIncomingArray(arr); - } - else - console.debug('Unknown encoding type: ' + type); - }; - this.send = function(msg) { - console.debug('send msg "' + msg + '"'); + this.sendQueue = ''; + this.sendTimeout = undefined; + this.doSend = function () { + that.sendTimeout = undefined; + console.debug('send msg "' + that.sendQueue + '"'); var req = new XMLHttpRequest(); - req.open('POST', this.getEndPoint('write')); - req.setRequestHeader('SessionId', this.sessionId); - if (this.sessionId === 'fetchsession') - { - req.responseType = 'text'; - req.addEventListener('load', function() { - console.debug('got session: ' + this.responseText); - that.sessionId = this.responseText; - that.readyState = 1; - that.onopen(); - }); - } + req.open('POST', that.getEndPoint('write')); + req.setRequestHeader('SessionId', that.sessionId); + if (that.sessionId === 'fetchsession') + console.debug('session fetch not completed'); else { req.responseType = 'arraybuffer'; req.addEventListener('load', function() { if (this.status == 200) - that.parseIncoming(this.responseType, this.response); + that.parseIncomingArray(new Uint8Array(this.response)); else console.debug('Error on incoming response'); }); } - req.send('B0x' + msg.length.toString(16) + '\n' + msg + '\n'); - }, + req.send(that.sendQueue); + that.sendQueue = ''; + }; + this.getSessionId = function() { + var req = new XMLHttpRequest(); + req.open('POST', that.getEndPoint('write')); + req.setRequestHeader('SessionId', that.sessionId); + req.responseType = 'text'; + req.addEventListener('load', function() { + console.debug('got session: ' + this.responseText); + that.sessionId = this.responseText; + that.readyState = 1; + that.onopen(); + }); + req.send(''); + }; + this.send = function(msg) { + this.sendQueue = this.sendQueue.concat( + 'B0x' + msg.length.toString(16) + '\n' + msg + '\n'); + if (this.sessionId !== 'fetchsession' && this.sendTimeout === undefined) + this.sendTimeout = setTimeout(this.doSend, 2 /* ms */); + }; this.close = function() { console.debug('close socket'); this.readyState = 3; @@ -296,7 +289,9 @@ }; console.debug('New proxy socket ' + this.id + ' ' + this.uri); - this.send('fetchsession'); + // queue fetch of session id. + this.getSessionId(); + var that = this; // horrors ... @@ -309,7 +304,7 @@ // fetch session id: req.addEventListener('load', function() { if (this.status == 200) - that.parseIncoming(this.responseType, this.response); + that.parseIncomingArray(new Uint8Array(this.response)); else console.debug('Handle error ' + this.status); that.readWaiting = false; commit b51605be81774af641b105ea42600e6f8a07dbd6 Author: Michael Meeks <michael.me...@collabora.com> AuthorDate: Thu Mar 19 15:54:28 2020 +0000 Commit: Michael Meeks <michael.me...@collabora.com> CommitDate: Sat Mar 21 14:07:24 2020 +0000 Proxy protocol bits. For now very silly: [T|B] + hex length + \n + content + \n Change-Id: I256b834a23cca975a705da2c569887665ac6be02 diff --git a/loleaflet/js/global.js b/loleaflet/js/global.js index 74e982873..c1edc9c7f 100644 --- a/loleaflet/js/global.js +++ b/loleaflet/js/global.js @@ -193,19 +193,97 @@ }; this.onmessage = function() { }; + this.parseIncomingArray = function(arr) { + var decoder = new TextDecoder(); + for (var i = 0; i < arr.length; ++i) + { + var left = arr.length - i; + if (left < 4) + { + console.debug('no data left'); + break; + } + var type = String.fromCharCode(arr[i+0]); + if (type != 'T' && type != 'B') + { + console.debug('wrong data type: ' + type); + break; + } + if (arr[i+1] !== 48 && arr[i+2] !== 120) // '0x' + { + console.debug('missing hex preamble'); + break; + } + i += 3; + var numStr = ''; + var start = i; + while (arr[i] != 10) // '\n' + i++; + numStr = decoder.decode(arr.slice(start, i)); // FIXME: IE11 + var size = parseInt(numStr, 16); + + i++; // skip \n + + var data; + if (type == 'T') // FIXME: IE11 + data = decoder.decode(arr.slice(i, i + size)); + else + data = arr.slice(i, i + size); + + this.onmessage({ data: data }); + + i += size; // skip trailing '\n' in loop-increment + } + }; + this.parseIncoming = function(type, msg) { + if (type === 'blob') + { + var fileReader = new FileReader(); + var that = this; + fileReader.onload = function(event) { + that.parseIncomingArray(event.target.result); + }; + fileReader.readAsArrayBuffer(msg); + } + else if (type === 'arraybuffer') + { + this.parseIncomingArray(new Uint8Array(msg)); + } + else if (type === 'text' || type === '') + { + const encoder = new TextEncoder() + const arr = encoder.encode(msg); + this.parseIncomingArray(arr); + } + else + console.debug('Unknown encoding type: ' + type); + }; this.send = function(msg) { console.debug('send msg "' + msg + '"'); var req = new XMLHttpRequest(); req.open('POST', this.getEndPoint('write')); req.setRequestHeader('SessionId', this.sessionId); if (this.sessionId === 'fetchsession') + { + req.responseType = 'text'; req.addEventListener('load', function() { console.debug('got session: ' + this.responseText); that.sessionId = this.responseText; that.readyState = 1; that.onopen(); }); - req.send(msg); + } + else + { + req.responseType = 'arraybuffer'; + req.addEventListener('load', function() { + if (this.status == 200) + that.parseIncoming(this.responseType, this.response); + else + console.debug('Error on incoming response'); + }); + } + req.send('B0x' + msg.length.toString(16) + '\n' + msg + '\n'); }, this.close = function() { console.debug('close socket'); @@ -218,7 +296,6 @@ }; console.debug('New proxy socket ' + this.id + ' ' + this.uri); - // FIXME: perhaps a little risky. this.send('fetchsession'); var that = this; @@ -231,20 +308,16 @@ var req = new XMLHttpRequest(); // fetch session id: req.addEventListener('load', function() { - console.debug('read: ' + this.responseText); if (this.status == 200) - { - that.onmessage({ data: this.response }); - } + that.parseIncoming(this.responseType, this.response); else - { console.debug('Handle error ' + this.status); - } that.readWaiting = false; }); req.open('GET', that.getEndPoint('read')); - req.setRequestHeader('SessionId', this.sessionId); - req.send(that.sessionId); + req.setRequestHeader('SessionId', that.sessionId); + req.responseType = 'arraybuffer'; + req.send(''); that.readWaiting = true; }, 250); }; diff --git a/net/Socket.hpp b/net/Socket.hpp index 2290dadd9..be05a5559 100644 --- a/net/Socket.hpp +++ b/net/Socket.hpp @@ -86,6 +86,10 @@ public: { _disposition = Type::CLOSED; } + std::shared_ptr<Socket> getSocket() const + { + return _socket; + } bool isMove() { return _disposition == Type::MOVE; } bool isClosed() { return _disposition == Type::CLOSED; } @@ -1035,6 +1039,12 @@ public: std::vector<std::pair<size_t, size_t>> _spans; }; + /// remove all queued input bytes + void clearInput() + { + _inBuffer.clear(); + } + /// Remove the first @count bytes from input buffer void eraseFirstInputBytes(const MessageMap &map) { @@ -1198,6 +1208,8 @@ public: /// Does it look like we have some TLS / SSL where we don't expect it ? bool sniffSSL() const; + void dumpState(std::ostream& os) override; + protected: /// Override to handle reading of socket data differently. virtual int readData(char* buf, int len) @@ -1221,8 +1233,6 @@ protected: #endif } - void dumpState(std::ostream& os) override; - void setShutdownSignalled(bool shutdownSignalled) { _shutdownSignalled = shutdownSignalled; diff --git a/wsd/DocumentBroker.hpp b/wsd/DocumentBroker.hpp index dd84960d1..cb5bf54e3 100644 --- a/wsd/DocumentBroker.hpp +++ b/wsd/DocumentBroker.hpp @@ -258,7 +258,7 @@ public: const Poco::URI& uriPublic, const bool isReadOnly, const std::string& hostNoTrust, - const std::shared_ptr<Socket> &moveSocket); + const std::shared_ptr<StreamSocket> &socket); /// Thread safe termination of this broker if it has a lingering thread void joinThread(); diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp index ed43262e8..514060624 100644 --- a/wsd/LOOLWSD.cpp +++ b/wsd/LOOLWSD.cpp @@ -2855,7 +2855,7 @@ private: { docBroker->handleProxyRequest( sessionId, id, uriPublic, isReadOnly, - hostNoTrust, moveSocket); + hostNoTrust, streamSocket); return; } catch (const UnauthorizedRequestException& exc) diff --git a/wsd/ProxyProtocol.cpp b/wsd/ProxyProtocol.cpp index 41043a57a..8aaff0131 100644 --- a/wsd/ProxyProtocol.cpp +++ b/wsd/ProxyProtocol.cpp @@ -25,7 +25,7 @@ void DocumentBroker::handleProxyRequest( const Poco::URI& uriPublic, const bool isReadOnly, const std::string& hostNoTrust, - const std::shared_ptr<Socket> &socket) + const std::shared_ptr<StreamSocket> &socket) { std::shared_ptr<ClientSession> clientSession; if (sessionId == "fetchsession") @@ -37,6 +37,22 @@ void DocumentBroker::handleProxyRequest( addSession(clientSession); LOOLWSD::checkDiskSpaceAndWarnClients(true); LOOLWSD::checkSessionLimitsAndWarnClients(); + + LOG_TRC("Returning id " << clientSession->getId()); + + std::ostringstream oss; + oss << "HTTP/1.1 200 OK\r\n" + "Last-Modified: " << Util::getHttpTimeNow() << "\r\n" + "User-Agent: " WOPI_AGENT_STRING "\r\n" + "Content-Length: " << clientSession->getId().size() << "\r\n" + "Content-Type: application/json\r\n" + "X-Content-Type-Options: nosniff\r\n" + "\r\n" + << clientSession->getId(); + + socket->send(oss.str()); + socket->shutdown(); + return; } else { @@ -69,13 +85,186 @@ void DocumentBroker::handleProxyRequest( proxy->handleRequest(uriPublic.toString(), socket); } +bool ProxyProtocolHandler::parseEmitIncoming( + const std::shared_ptr<StreamSocket> &socket) +{ + std::vector<char> &in = socket->getInBuffer(); + + std::stringstream oss; + socket->dumpState(oss); + LOG_TRC("Parse message:\n" << oss.str()); + + while (in.size() > 0) + { + if (in[0] != 'T' && in[0] != 'B') + { + LOG_ERR("Invalid message type " << in[0]); + return false; + } + auto it = in.begin() + 1; + for (; it != in.end() && *it != '\n'; ++it); + *it = '\0'; + uint64_t len = strtoll( &in[1], nullptr, 16 ); + in.erase(in.begin(), it + 1); + if (len > in.size()) + { + LOG_ERR("Invalid message length " << len << " vs " << in.size()); + return false; + } + // far from efficient: + std::vector<char> data; + data.insert(data.begin(), in.begin(), in.begin() + len + 1); + in.erase(in.begin(), in.begin() + len); + + if (in.size() < 1 || in[0] != '\n') + { + LOG_ERR("Missing final newline"); + return false; + } + in.erase(in.begin(), in.begin() + 1); + + _msgHandler->handleMessage(data); + } + return true; +} + void ProxyProtocolHandler::handleRequest(const std::string &uriPublic, const std::shared_ptr<Socket> &socket) { + auto streamSocket = std::static_pointer_cast<StreamSocket>(socket); + bool bRead = uriPublic.find("/write") == std::string::npos; LOG_INF("Proxy handle request " << uriPublic << " type: " << (bRead ? "read" : "write")); - (void)socket; + + if (bRead) + { + if (!_msgHandler) + LOG_WRN("unusual - incoming message with no-one to handle it"); + else if (!parseEmitIncoming(streamSocket)) + { + std::stringstream oss; + streamSocket->dumpState(oss); + LOG_ERR("bad socket structure " << oss.str()); + } + } + + if (!flushQueueTo(streamSocket) && !bRead) + { + // longer running 'write socket' + _writeSockets.push_back(streamSocket); + } + else + socket->shutdown(); +} + +void ProxyProtocolHandler::handleIncomingMessage(SocketDisposition &disposition) +{ + std::stringstream oss; + disposition.getSocket()->dumpState(oss); + LOG_ERR("If you got here, it means we failed to parse this properly in handleRequest: " << oss.str()); +} + +int ProxyProtocolHandler::sendMessage(const char *msg, const size_t len, bool text, bool flush) +{ + _writeQueue.push_back(std::make_shared<Message>(msg, len, text)); + auto sock = popWriteSocket(); + if (sock && flush) + { + flushQueueTo(sock); + sock->shutdown(); + } + + return len; +} + +int ProxyProtocolHandler::sendTextMessage(const char *msg, const size_t len, bool flush) const +{ + LOG_TRC("ProxyHack - send text msg " + std::string(msg, len)); + return const_cast<ProxyProtocolHandler *>(this)->sendMessage(msg, len, true, flush); +} + +int ProxyProtocolHandler::sendBinaryMessage(const char *data, const size_t len, bool flush) const +{ + LOG_TRC("ProxyHack - send binary msg len " << len); + return const_cast<ProxyProtocolHandler *>(this)->sendMessage(data, len, false, flush); +} + +void ProxyProtocolHandler::shutdown(bool goingAway, const std::string &statusMessage) +{ + LOG_TRC("ProxyHack - shutdown " << goingAway << ": " << statusMessage); +} + +void ProxyProtocolHandler::getIOStats(uint64_t &sent, uint64_t &recv) +{ + sent = recv = 0; +} + +void ProxyProtocolHandler::dumpState(std::ostream& os) +{ + os << "proxy protocol sockets: " << _writeSockets.size() << " writeQueue: " << _writeQueue.size() << ":\n"; + for (auto it : _writeQueue) + Util::dumpHex(os, "\twrite queue entry:", "\t\t", *it); +} + +void ProxyProtocolHandler::performWrites() +{ + if (_msgHandler) + _msgHandler->writeQueuedMessages(); + if (_writeQueue.size() <= 0) + return; + + auto sock = popWriteSocket(); + if (sock) + { + flushQueueTo(sock); + sock->shutdown(); + } +} + +bool ProxyProtocolHandler::flushQueueTo(const std::shared_ptr<StreamSocket> &socket) +{ + // slurp from the core to us. + if (_msgHandler && _msgHandler->hasQueuedMessages()) + _msgHandler->writeQueuedMessages(); + + size_t totalSize = 0; + for (auto it : _writeQueue) + totalSize += it->size(); + + if (!totalSize) + return false; + + std::ostringstream oss; + oss << "HTTP/1.1 200 OK\r\n" + "Last-Modified: " << Util::getHttpTimeNow() << "\r\n" + "User-Agent: " WOPI_AGENT_STRING "\r\n" + "Content-Length: " << totalSize << "\r\n" + "Content-Type: application/json\r\n" + "X-Content-Type-Options: nosniff\r\n" + "\r\n"; + socket->send(oss.str()); + + for (auto it : _writeQueue) + socket->send(it->data(), it->size(), false); + _writeQueue.clear(); + + return true; +} + +// LRU-ness ... +std::shared_ptr<StreamSocket> ProxyProtocolHandler::popWriteSocket() +{ + std::weak_ptr<StreamSocket> sock; + while (!_writeSockets.empty()) + { + sock = _writeSockets.front(); + _writeSockets.erase(_writeSockets.begin()); + auto realSock = sock.lock(); + if (realSock) + return realSock; + } + return std::shared_ptr<StreamSocket>(); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/wsd/ProxyProtocol.hpp b/wsd/ProxyProtocol.hpp index 1f88e1fa7..091ac3295 100644 --- a/wsd/ProxyProtocol.hpp +++ b/wsd/ProxyProtocol.hpp @@ -10,19 +10,21 @@ #ifndef INCLUDED_PROXY_PROTOCOL_HPP #define INCLUDED_PROXY_PROTOCOL_HPP +#include <memory> #include <net/Socket.hpp> -/// Interface for building a websocket from this ... +/** + * Implementation that builds a websocket like protocol from many + * individual proxied HTTP requests back to back. + * + * we use a trivial framing: <hex-length>\r\n<content>\r\n + */ class ProxyProtocolHandler : public ProtocolHandlerInterface { public: - ProxyProtocolHandler() - { - } + ProxyProtocolHandler() { } - virtual ~ProxyProtocolHandler() - { - } + virtual ~ProxyProtocolHandler() { } /// Will be called exactly once by setHandler void onConnect(const std::shared_ptr<StreamSocket>& /* socket */) override @@ -30,10 +32,7 @@ public: } /// Called after successful socket reads. - void handleIncomingMessage(SocketDisposition &/* disposition */) override - { - assert("we get our data a different way" && false); - } + void handleIncomingMessage(SocketDisposition &/* disposition */) override; int getPollEvents(std::chrono::steady_clock::time_point /* now */, int &/* timeoutMaxMs */) override @@ -46,9 +45,7 @@ public: { } - void performWrites() override - { - } + void performWrites() override; void onDisconnect() override { @@ -59,40 +56,40 @@ public: /// Clear all external references virtual void dispose() { _msgHandler.reset(); } - int sendTextMessage(const char *msg, const size_t len, bool flush = false) const override - { - LOG_TRC("ProxyHack - send text msg " + std::string(msg, len)); - (void) flush; - return len; - } + int sendTextMessage(const char *msg, const size_t len, bool flush = false) const override; + int sendBinaryMessage(const char *data, const size_t len, bool flush = false) const override; + void shutdown(bool goingAway = false, const std::string &statusMessage = "") override; + void getIOStats(uint64_t &sent, uint64_t &recv) override; + void dumpState(std::ostream& os); - int sendBinaryMessage(const char *data, const size_t len, bool flush = false) const override - { - (void) data; (void) flush; - LOG_TRC("ProxyHack - send binary msg len " << len); - return len; - } - - void shutdown(bool goingAway = false, const std::string &statusMessage = "") override - { - LOG_TRC("ProxyHack - shutdown " << goingAway << ": " << statusMessage); - } - - void getIOStats(uint64_t &sent, uint64_t &recv) override - { - sent = recv = 0; - } - - void dumpState(std::ostream& os) - { - os << "proxy protocol\n"; - } + bool parseEmitIncoming(const std::shared_ptr<StreamSocket> &socket); void handleRequest(const std::string &uriPublic, const std::shared_ptr<Socket> &socket); private: - std::vector<std::weak_ptr<StreamSocket>> _sockets; + std::shared_ptr<StreamSocket> popWriteSocket(); + int sendMessage(const char *msg, const size_t len, bool text, bool flush); + bool flushQueueTo(const std::shared_ptr<StreamSocket> &socket); + + struct Message : public std::vector<char> + { + Message(const char *msg, const size_t len, bool text) + { + const char *type = text ? "T" : "B"; + insert(end(), type, type + 1); + std::ostringstream os; + os << std::hex << "0x" << len << "\n"; + std::string str = os.str(); + insert(end(), str.c_str(), str.c_str() + str.size()); + insert(end(), msg, msg + len); + const char *terminator = "\n"; + insert(end(), terminator, terminator + 1); + } + }; + /// queue things when we have no socket to hand. + std::vector<std::shared_ptr<Message>> _writeQueue; + std::vector<std::weak_ptr<StreamSocket>> _writeSockets; }; #endif commit c4fc7b788215c7549ebe60895de5c3df0216ff31 Author: Michael Meeks <michael.me...@collabora.com> AuthorDate: Wed Mar 4 13:54:04 2020 +0000 Commit: Michael Meeks <michael.me...@collabora.com> CommitDate: Sat Mar 21 14:07:24 2020 +0000 Proxy websocket prototype. Try to read/write avoiding a websocket. Change-Id: I382039fa88f1030a63df1e47f687df2ee5a6055b diff --git a/Makefile.am b/Makefile.am index e4e6ed5db..92d87fa50 100644 --- a/Makefile.am +++ b/Makefile.am @@ -112,6 +112,7 @@ loolwsd_sources = common/Crypto.cpp \ wsd/AdminModel.cpp \ wsd/Auth.cpp \ wsd/DocumentBroker.cpp \ + wsd/ProxyProtocol.cpp \ wsd/LOOLWSD.cpp \ wsd/ClientSession.cpp \ wsd/FileServer.cpp \ @@ -203,6 +204,7 @@ wsd_headers = wsd/Admin.hpp \ wsd/Auth.hpp \ wsd/ClientSession.hpp \ wsd/DocumentBroker.hpp \ + wsd/ProxyProtocol.hpp \ wsd/Exceptions.hpp \ wsd/FileServer.hpp \ wsd/LOOLWSD.hpp \ diff --git a/loleaflet/js/global.js b/loleaflet/js/global.js index a08c4cf3b..74e982873 100644 --- a/loleaflet/js/global.js +++ b/loleaflet/js/global.js @@ -166,16 +166,97 @@ }; this.onopen = function() { }; + this.close = function() { + }; }; - - global.FakeWebSocket.prototype.close = function() { - }; - global.FakeWebSocket.prototype.send = function(data) { this.sendCounter++; window.postMobileMessage(data); }; + global.proxySocketCounter = 0; + global.ProxySocket = function (uri) { + this.uri = uri; + this.binaryType = 'arraybuffer'; + this.bufferedAmount = 0; + this.extensions = ''; + this.protocol = ''; + this.connected = true; + this.readyState = 0; // connecting + this.sessionId = 'fetchsession'; + this.id = window.proxySocketCounter++; + this.sendCounter = 0; + this.readWaiting = false; + this.onclose = function() { + }; + this.onerror = function() { + }; + this.onmessage = function() { + }; + this.send = function(msg) { + console.debug('send msg "' + msg + '"'); + var req = new XMLHttpRequest(); + req.open('POST', this.getEndPoint('write')); + req.setRequestHeader('SessionId', this.sessionId); + if (this.sessionId === 'fetchsession') + req.addEventListener('load', function() { + console.debug('got session: ' + this.responseText); + that.sessionId = this.responseText; + that.readyState = 1; + that.onopen(); + }); + req.send(msg); + }, + this.close = function() { + console.debug('close socket'); + this.readyState = 3; + this.onclose(); + }; + this.getEndPoint = function(type) { + var base = this.uri; + return base.replace(/^ws/, 'http') + '/' + type; + }; + console.debug('New proxy socket ' + this.id + ' ' + this.uri); + + // FIXME: perhaps a little risky. + this.send('fetchsession'); + var that = this; + + // horrors ... + this.readInterval = setInterval(function() { + if (this.readWaiting) // one at a time for now + return; + if (this.sessionId == 'fetchsession') + return; // waiting for our session id. + var req = new XMLHttpRequest(); + // fetch session id: + req.addEventListener('load', function() { + console.debug('read: ' + this.responseText); + if (this.status == 200) + { + that.onmessage({ data: this.response }); + } + else + { + console.debug('Handle error ' + this.status); + } + that.readWaiting = false; + }); + req.open('GET', that.getEndPoint('read')); + req.setRequestHeader('SessionId', this.sessionId); + req.send(that.sessionId); + that.readWaiting = true; + }, 250); + }; + + global.createWebSocket = function(uri) { + if (global.socketProxy) { + return new global.ProxySocket(uri); + } else { + return new WebSocket(uri); + } + }; + // If not debug, don't print anything on the console // except in tile debug mode (Ctrl-Shift-Alt-d) console.log2 = console.log; @@ -200,7 +281,8 @@ window.postMobileError(log); } else if (global.socket && (global.socket instanceof WebSocket) && global.socket.readyState === 1) { global.socket.send(log); - } else if (global.socket && (global.socket instanceof global.L.Socket) && global.socket.connected()) { + } else if (global.socket && global.L && global.L.Socket && + (global.socket instanceof global.L.Socket) && global.socket.connected()) { global.socket.sendMessage(log); } else { var req = new XMLHttpRequest(); @@ -275,7 +357,7 @@ var websocketURI = global.host + global.serviceRoot + '/lool/' + encodeURIComponent(global.docURL + (docParams ? '?' + docParams : '')) + '/ws' + wopiSrc; try { - global.socket = new WebSocket(websocketURI); + global.socket = global.createWebSocket(websocketURI); } catch (err) { console.log(err); } diff --git a/loleaflet/src/core/Socket.js b/loleaflet/src/core/Socket.js index b4118479f..f4a44fd06 100644 --- a/loleaflet/src/core/Socket.js +++ b/loleaflet/src/core/Socket.js @@ -49,7 +49,7 @@ L.Socket = L.Class.extend({ } try { - this.socket = new WebSocket(this.getWebSocketBaseURI(map) + wopiSrc); + this.socket = window.createWebSocket(this.getWebSocketBaseURI(map) + wopiSrc); } catch (e) { // On IE 11 there is a limitation on the number of WebSockets open to a single domain (6 by default and can go to 128). // Detect this and hint the user. diff --git a/net/Socket.hpp b/net/Socket.hpp index a6395b9b4..2290dadd9 100644 --- a/net/Socket.hpp +++ b/net/Socket.hpp @@ -446,6 +446,11 @@ public: } } + std::shared_ptr<ProtocolHandlerInterface> getProtocol() const + { + return _protocol; + } + /// Do we have something to send ? virtual bool hasQueuedMessages() const = 0; /// Please send them to me then. diff --git a/wsd/ClientSession.hpp b/wsd/ClientSession.hpp index b47285dd0..e347dbd74 100644 --- a/wsd/ClientSession.hpp +++ b/wsd/ClientSession.hpp @@ -37,9 +37,6 @@ public: void construct(); virtual ~ClientSession(); - /// Lookup any session by id. - static std::shared_ptr<ClientSession> getById(const std::string &id); - void setReadOnly() override; enum SessionState { @@ -288,7 +285,6 @@ private: std::chrono::steady_clock::time_point _viewLoadStart; }; - #endif /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/wsd/DocumentBroker.hpp b/wsd/DocumentBroker.hpp index 68369d274..dd84960d1 100644 --- a/wsd/DocumentBroker.hpp +++ b/wsd/DocumentBroker.hpp @@ -251,6 +251,15 @@ public: const bool isReadOnly, const std::string& hostNoTrust); + /// Find or create a new client session for the PHP proxy + void handleProxyRequest( + const std::string& sessionId, + const std::string& id, + const Poco::URI& uriPublic, + const bool isReadOnly, + const std::string& hostNoTrust, + const std::shared_ptr<Socket> &moveSocket); + /// Thread safe termination of this broker if it has a lingering thread void joinThread(); diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp index aa6b9766b..ed43262e8 100644 --- a/wsd/LOOLWSD.cpp +++ b/wsd/LOOLWSD.cpp @@ -238,6 +238,9 @@ namespace #if ENABLE_SUPPORT_KEY inline void shutdownLimitReached(const std::shared_ptr<ProtocolHandlerInterface>& proto) { + if (!proto) + return; + const std::string error = Poco::format(PAYLOAD_UNAVAILABLE_LIMIT_REACHED, LOOLWSD::MaxDocuments, LOOLWSD::MaxConnections); LOG_INF("Sending client 'hardlimitreached' message: " << error); @@ -1764,9 +1767,12 @@ static std::shared_ptr<DocumentBroker> if (docBroker->isMarkedToDestroy()) { LOG_WRN("DocBroker with docKey [" << docKey << "] that is marked to be destroyed. Rejecting client request."); - std::string msg("error: cmd=load kind=docunloading"); - proto->sendTextMessage(msg.data(), msg.size()); - proto->shutdown(true, "error: cmd=load kind=docunloading"); + if (proto) + { + std::string msg("error: cmd=load kind=docunloading"); + proto->sendTextMessage(msg.data(), msg.size()); + proto->shutdown(true, "error: cmd=load kind=docunloading"); + } return nullptr; } } @@ -1782,9 +1788,12 @@ static std::shared_ptr<DocumentBroker> } // Indicate to the client that we're connecting to the docbroker. - const std::string statusConnect = "statusindicator: connect"; - LOG_TRC("Sending to Client [" << statusConnect << "]."); - proto->sendTextMessage(statusConnect.data(), statusConnect.size()); + if (proto) + { + const std::string statusConnect = "statusindicator: connect"; + LOG_TRC("Sending to Client [" << statusConnect << "]."); + proto->sendTextMessage(statusConnect.data(), statusConnect.size()); + } if (!docBroker) { @@ -2242,6 +2251,13 @@ private: // Util::dumpHex(std::cerr, "clipboard:\n", "", socket->getInBuffer()); // lots of data ... handleClipboardRequest(request, message, disposition); } + else if (request.has("ProxyPrefix") && reqPathTokens.count() > 2 && + (reqPathTokens[reqPathTokens.count()-2] == "ws")) + { + std::string decodedUri; // WOPISrc + Poco::URI::decode(reqPathTokens[1], decodedUri); + handleClientProxyRequest(request, decodedUri, message, disposition); + } else if (!(request.find("Upgrade") != request.end() && Poco::icompare(request["Upgrade"], "websocket") == 0) && reqPathTokens.count() > 0 && reqPathTokens[0] == "lool") { @@ -2775,6 +2791,99 @@ private: } #endif + void handleClientProxyRequest(const Poco::Net::HTTPRequest& request, + std::string url, + Poco::MemoryInputStream& message, + SocketDisposition &disposition) + { + if (!request.has("SessionId")) + throw BadRequestException("No session id header on proxied request"); + + std::string sessionId = request.get("SessionId"); + + LOG_INF("URL [" << url << "]."); + const auto uriPublic = DocumentBroker::sanitizeURI(url); + LOG_INF("URI [" << uriPublic.getPath() << "]."); + const auto docKey = DocumentBroker::getDocKey(uriPublic); + LOG_INF("DocKey [" << docKey << "]."); + const std::string fileId = Util::getFilenameFromURL(docKey); + Util::mapAnonymized(fileId, fileId); // Identity mapping, since fileId is already obfuscated + + LOG_INF("Starting Proxy request handler for session [" << _id << "] on url [" << LOOLWSD::anonymizeUrl(url) << "]."); + + // Check if readonly session is required + bool isReadOnly = false; + for (const auto& param : uriPublic.getQueryParameters()) + { + LOG_DBG("Query param: " << param.first << ", value: " << param.second); + if (param.first == "permission" && param.second == "readonly") + { + isReadOnly = true; + } + } + + const std::string hostNoTrust = (LOOLWSD::ServerName.empty() ? request.getHost() : LOOLWSD::ServerName); + + LOG_INF("URL [" << LOOLWSD::anonymizeUrl(url) << "] is " << (isReadOnly ? "readonly" : "writable") << "."); + (void)request; (void)message; (void)disposition; + + std::shared_ptr<ProtocolHandlerInterface> none; + // Request a kit process for this doc. + std::shared_ptr<DocumentBroker> docBroker = findOrCreateDocBroker( + none, url, docKey, _id, uriPublic); + if (docBroker) + { + // need to move into the DocumentBroker context before doing session lookup / creation etc. + std::string id = _id; + disposition.setMove([docBroker, id, uriPublic, isReadOnly, hostNoTrust, sessionId] + (const std::shared_ptr<Socket> &moveSocket) + { + LOG_TRC("Setting up docbroker thread for " << docBroker->getDocKey()); + // Make sure the thread is running before adding callback. + docBroker->startThread(); + + // We no longer own this socket. + moveSocket->setThreadOwner(std::thread::id()); + + docBroker->addCallback([docBroker, id, uriPublic, isReadOnly, hostNoTrust, sessionId, moveSocket]() + { + // Now inside the document broker thread ... + LOG_TRC("In the docbroker thread for " << docBroker->getDocKey()); + + auto streamSocket = std::static_pointer_cast<StreamSocket>(moveSocket); + try + { + docBroker->handleProxyRequest( + sessionId, id, uriPublic, isReadOnly, + hostNoTrust, moveSocket); + return; + } + catch (const UnauthorizedRequestException& exc) + { + LOG_ERR("Unauthorized Request while loading session for " << docBroker->getDocKey() << ": " << exc.what()); + } + catch (const StorageConnectionException& exc) + { + LOG_ERR("Error while loading : " << exc.what()); + } + catch (const std::exception& exc) + { + LOG_ERR("Error while loading : " << exc.what()); + } + // badness occured: + std::ostringstream oss; + oss << "HTTP/1.1 400\r\n" + << "Date: " << Util::getHttpTimeNow() << "\r\n" + << "User-Agent: LOOLWSD WOPI Agent\r\n" + << "Content-Length: 0\r\n" + << "\r\n"; + streamSocket->send(oss.str()); + streamSocket->shutdown(); + }); + }); + } + } + void handleClientWsUpgrade(const Poco::Net::HTTPRequest& request, const std::string& url, SocketDisposition &disposition) { @@ -2837,7 +2946,7 @@ private: // Request a kit process for this doc. std::shared_ptr<DocumentBroker> docBroker = findOrCreateDocBroker( std::static_pointer_cast<ProtocolHandlerInterface>(ws), url, docKey, _id, uriPublic); - if (docBroker) + if (docBroker) { #if MOBILEAPP const std::string hostNoTrust; diff --git a/wsd/ProxyProtocol.cpp b/wsd/ProxyProtocol.cpp new file mode 100644 index 000000000..41043a57a --- /dev/null +++ b/wsd/ProxyProtocol.cpp @@ -0,0 +1,81 @@ +/* -*- 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 "DocumentBroker.hpp" +#include "ClientSession.hpp" +#include "ProxyProtocol.hpp" +#include "Exceptions.hpp" +#include "LOOLWSD.hpp" +#include <Socket.hpp> + +#include <atomic> +#include <cassert> + +void DocumentBroker::handleProxyRequest( + const std::string& sessionId, + const std::string& id, + const Poco::URI& uriPublic, + const bool isReadOnly, + const std::string& hostNoTrust, + const std::shared_ptr<Socket> &socket) +{ + std::shared_ptr<ClientSession> clientSession; + if (sessionId == "fetchsession") + { + LOG_TRC("Create session for " << _docKey); + clientSession = createNewClientSession( + std::make_shared<ProxyProtocolHandler>(), + id, uriPublic, isReadOnly, hostNoTrust); + addSession(clientSession); + LOOLWSD::checkDiskSpaceAndWarnClients(true); + LOOLWSD::checkSessionLimitsAndWarnClients(); + } + else + { + LOG_TRC("Find session for " << _docKey << " with id " << sessionId); + for (const auto &it : _sessions) + { + if (it.second->getId() == sessionId) + { + clientSession = it.second; + break; + } + } + if (!clientSession) + { + LOG_ERR("Invalid session id used " << sessionId); + throw BadRequestException("invalid session id"); + } + } + + auto protocol = clientSession->getProtocol(); + auto streamSocket = std::static_pointer_cast<StreamSocket>(socket); + streamSocket->setHandler(protocol); + + // this DocumentBroker's poll handles reading & writing + addSocketToPoll(socket); + + auto proxy = std::static_pointer_cast<ProxyProtocolHandler>( + protocol); + + proxy->handleRequest(uriPublic.toString(), socket); +} + +void ProxyProtocolHandler::handleRequest(const std::string &uriPublic, + const std::shared_ptr<Socket> &socket) +{ + bool bRead = uriPublic.find("/write") == std::string::npos; + LOG_INF("Proxy handle request " << uriPublic << " type: " << + (bRead ? "read" : "write")); + (void)socket; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/wsd/ProxyProtocol.hpp b/wsd/ProxyProtocol.hpp new file mode 100644 index 000000000..1f88e1fa7 --- /dev/null +++ b/wsd/ProxyProtocol.hpp @@ -0,0 +1,100 @@ +/* -*- 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/. + */ + +#ifndef INCLUDED_PROXY_PROTOCOL_HPP +#define INCLUDED_PROXY_PROTOCOL_HPP + +#include <net/Socket.hpp> + +/// Interface for building a websocket from this ... +class ProxyProtocolHandler : public ProtocolHandlerInterface +{ +public: + ProxyProtocolHandler() + { + } + + virtual ~ProxyProtocolHandler() + { + } + + /// Will be called exactly once by setHandler + void onConnect(const std::shared_ptr<StreamSocket>& /* socket */) override + { + } + + /// Called after successful socket reads. + void handleIncomingMessage(SocketDisposition &/* disposition */) override + { + assert("we get our data a different way" && false); + } + + int getPollEvents(std::chrono::steady_clock::time_point /* now */, + int &/* timeoutMaxMs */) override + { + // underlying buffer based polling is fine. + return POLLIN; + } + + void checkTimeout(std::chrono::steady_clock::time_point /* now */) override + { + } + + void performWrites() override + { + } + + void onDisconnect() override + { + // connections & sockets come and go a lot. + } + +public: + /// Clear all external references + virtual void dispose() { _msgHandler.reset(); } + + int sendTextMessage(const char *msg, const size_t len, bool flush = false) const override + { + LOG_TRC("ProxyHack - send text msg " + std::string(msg, len)); + (void) flush; + return len; + } + + int sendBinaryMessage(const char *data, const size_t len, bool flush = false) const override + { + (void) data; (void) flush; + LOG_TRC("ProxyHack - send binary msg len " << len); + return len; + } + + void shutdown(bool goingAway = false, const std::string &statusMessage = "") override + { + LOG_TRC("ProxyHack - shutdown " << goingAway << ": " << statusMessage); + } + + void getIOStats(uint64_t &sent, uint64_t &recv) override + { + sent = recv = 0; + } + + void dumpState(std::ostream& os) + { + os << "proxy protocol\n"; + } + + void handleRequest(const std::string &uriPublic, + const std::shared_ptr<Socket> &socket); + +private: + std::vector<std::weak_ptr<StreamSocket>> _sockets; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ commit 52204d304055e66de0aac22ac0c3ee1bc8c7bdd1 Author: Michael Meeks <michael.me...@collabora.com> AuthorDate: Wed Mar 4 13:52:51 2020 +0000 Commit: Michael Meeks <michael.me...@collabora.com> CommitDate: Sat Mar 21 14:07:24 2020 +0000 ProxyPrefix: allow the user to specify a custom prefix. This allows us to re-direct web traffic via a proxy quite simply during fetch, instead of changing the service root. Change-Id: I28d348467e48394d581fca4da4c199348a2ca8e0 diff --git a/loleaflet/html/loleaflet.html.m4 b/loleaflet/html/loleaflet.html.m4 index 84fd50b15..86e54249f 100644 --- a/loleaflet/html/loleaflet.html.m4 +++ b/loleaflet/html/loleaflet.html.m4 @@ -235,6 +235,7 @@ m4_ifelse(MOBILEAPP,[true], window.reuseCookies = ''; window.protocolDebug = false; window.frameAncestors = ''; + window.socketProxy = false; window.tileSize = 256;], [window.host = '%HOST%'; window.serviceRoot = '%SERVICE_ROOT%'; @@ -247,6 +248,7 @@ m4_ifelse(MOBILEAPP,[true], window.reuseCookies = '%REUSE_COOKIES%'; window.protocolDebug = %PROTOCOL_DEBUG%; window.frameAncestors = '%FRAME_ANCESTORS%'; + window.socketProxy = %SOCKET_PROXY%; window.tileSize = 256;]) m4_syscmd([cat ]GLOBAL_JS)m4_dnl </script> diff --git a/wsd/FileServer.cpp b/wsd/FileServer.cpp index ce9e12756..2b53999c8 100644 --- a/wsd/FileServer.cpp +++ b/wsd/FileServer.cpp @@ -595,6 +595,17 @@ constexpr char BRANDING[] = "branding"; constexpr char BRANDING_UNSUPPORTED[] = "branding-unsupported"; #endif +namespace { + // The user can override the ServerRoot with a new prefix. + std::string getResponseRoot(const HTTPRequest &request) + { + if (!request.has("ProxyPrefix")) + return LOOLWSD::ServiceRoot; + std::string proxyPrefix = request.get("ProxyPrefix", ""); + return proxyPrefix; + } +} + void FileServerRequestHandler::preprocessFile(const HTTPRequest& request, Poco::MemoryInputStream& message, const std::shared_ptr<StreamSocket>& socket) { @@ -641,15 +652,21 @@ void FileServerRequestHandler::preprocessFile(const HTTPRequest& request, Poco:: } } - const auto& config = Application::instance().config(); + std::string socketProxy = "false"; + if (request.has("ProxyPrefix")) + socketProxy = "true"; + Poco::replaceInPlace(preprocess, std::string("%SOCKET_PROXY%"), socketProxy); + + std::string responseRoot = getResponseRoot(request); Poco::replaceInPlace(preprocess, std::string("%ACCESS_TOKEN%"), escapedAccessToken); Poco::replaceInPlace(preprocess, std::string("%ACCESS_TOKEN_TTL%"), std::to_string(tokenTtl)); Poco::replaceInPlace(preprocess, std::string("%ACCESS_HEADER%"), escapedAccessHeader); Poco::replaceInPlace(preprocess, std::string("%HOST%"), host); Poco::replaceInPlace(preprocess, std::string("%VERSION%"), std::string(LOOLWSD_VERSION_HASH)); - Poco::replaceInPlace(preprocess, std::string("%SERVICE_ROOT%"), LOOLWSD::ServiceRoot); + Poco::replaceInPlace(preprocess, std::string("%SERVICE_ROOT%"), responseRoot); + const auto& config = Application::instance().config(); std::string protocolDebug = "false"; if (config.getBool("logging.protocol")) protocolDebug = "true"; @@ -658,16 +675,16 @@ void FileServerRequestHandler::preprocessFile(const HTTPRequest& request, Poco:: static const std::string linkCSS("<link rel=\"stylesheet\" href=\"%s/loleaflet/" LOOLWSD_VERSION_HASH "/%s.css\">"); static const std::string scriptJS("<script src=\"%s/loleaflet/" LOOLWSD_VERSION_HASH "/%s.js\"></script>"); - std::string brandCSS(Poco::format(linkCSS, LOOLWSD::ServiceRoot, std::string(BRANDING))); - std::string brandJS(Poco::format(scriptJS, LOOLWSD::ServiceRoot, std::string(BRANDING))); + std::string brandCSS(Poco::format(linkCSS, responseRoot, std::string(BRANDING))); + std::string brandJS(Poco::format(scriptJS, responseRoot, std::string(BRANDING))); #if ENABLE_SUPPORT_KEY const std::string keyString = config.getString("support_key", ""); SupportKey key(keyString); if (!key.verify() || key.validDaysRemaining() <= 0) { - brandCSS = Poco::format(linkCSS, LOOLWSD::ServiceRoot, std::string(BRANDING_UNSUPPORTED)); - brandJS = Poco::format(scriptJS, LOOLWSD::ServiceRoot, std::string(BRANDING_UNSUPPORTED)); + brandCSS = Poco::format(linkCSS, responseRoot, std::string(BRANDING_UNSUPPORTED)); + brandJS = Poco::format(scriptJS, responseRoot, std::string(BRANDING_UNSUPPORTED)); } #endif @@ -850,13 +867,15 @@ void FileServerRequestHandler::preprocessAdminFile(const HTTPRequest& request,co if (!FileServerRequestHandler::isAdminLoggedIn(request, response)) throw Poco::Net::NotAuthenticatedException("Invalid admin login"); + std::string responseRoot = getResponseRoot(request); + static const std::string scriptJS("<script src=\"%s/loleaflet/" LOOLWSD_VERSION_HASH "/%s.js\"></script>"); static const std::string footerPage("<div class=\"footer navbar-fixed-bottom text-info text-center\"><strong>Key:</strong> %s <strong>Expiry Date:</strong> %s</div>"); const std::string relPath = getRequestPathname(request); LOG_DBG("Preprocessing file: " << relPath); std::string adminFile = *getUncompressedFile(relPath); - std::string brandJS(Poco::format(scriptJS, LOOLWSD::ServiceRoot, std::string(BRANDING))); + std::string brandJS(Poco::format(scriptJS, responseRoot, std::string(BRANDING))); std::string brandFooter; #if ENABLE_SUPPORT_KEY @@ -874,7 +893,7 @@ void FileServerRequestHandler::preprocessAdminFile(const HTTPRequest& request,co Poco::replaceInPlace(adminFile, std::string("<!--%BRANDING_JS%-->"), brandJS); Poco::replaceInPlace(adminFile, std::string("<!--%FOOTER%-->"), brandFooter); Poco::replaceInPlace(adminFile, std::string("%VERSION%"), std::string(LOOLWSD_VERSION_HASH)); - Poco::replaceInPlace(adminFile, std::string("%SERVICE_ROOT%"), LOOLWSD::ServiceRoot); + Poco::replaceInPlace(adminFile, std::string("%SERVICE_ROOT%"), responseRoot); // Ask UAs to block if they detect any XSS attempt response.add("X-XSS-Protection", "1; mode=block"); commit d26aa30cab3335df110110d86fa42750529e9b98 Author: Miklos Vajna <vmik...@collabora.com> AuthorDate: Fri Mar 20 18:04:29 2020 +0100 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Mar 20 22:05:14 2020 +0100 Fix --with-support-public-key=... build Change-Id: I4a1ecc16d7b862dae61c80d98d07c0e105c13819 Reviewed-on: https://gerrit.libreoffice.org/c/online/+/90829 Tested-by: Andras Timar <andras.ti...@collabora.com> Reviewed-by: Andras Timar <andras.ti...@collabora.com> diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp index 177360dbc..aa6b9766b 100644 --- a/wsd/LOOLWSD.cpp +++ b/wsd/LOOLWSD.cpp @@ -244,7 +244,7 @@ inline void shutdownLimitReached(const std::shared_ptr<ProtocolHandlerInterface> try { // Let the client know we are shutting down. - proto->sendTextMessage(error); + proto->sendTextMessage(error.data(), error.size()); // Shutdown. proto->shutdown(true, error); commit d474f060409328c1f40c63074396b1bc579de72e Author: Tor Lillqvist <t...@collabora.com> AuthorDate: Fri Mar 20 16:02:08 2020 +0200 Commit: Tor Lillqvist <t...@collabora.com> CommitDate: Fri Mar 20 16:03:19 2020 +0200 Force portrait for the moment also for iPhone in the iOS app Change-Id: I1def28e7969cea753e7fc36094fe6514c17d61af diff --git a/ios/Mobile/Info.plist.in b/ios/Mobile/Info.plist.in index 628e95e7b..33517ab14 100644 --- a/ios/Mobile/Info.plist.in +++ b/ios/Mobile/Info.plist.in @@ -430,8 +430,6 @@ <key>UISupportedInterfaceOrientations</key> <array> <string>UIInterfaceOrientationPortrait</string> - <string>UIInterfaceOrientationLandscapeLeft</string> - <string>UIInterfaceOrientationLandscapeRight</string> </array> <key>UISupportedInterfaceOrientations~ipad</key> <array> commit a4a5b4aed0992c8430de7414e3a6ba12bdf91fd5 Author: Henry Castro <hcas...@collabora.com> AuthorDate: Fri Mar 20 08:37:21 2020 -0400 Commit: Henry Castro <hcas...@collabora.com> CommitDate: Fri Mar 20 14:40:12 2020 +0100 loleaflet: move the code about init of the style combobox, part 2 It fixes mobile: TypeError: Cannot read property 'length' of undefined Change-Id: I620833dfdd736347da86d76531cf1c67e0b5b152 Reviewed-on: https://gerrit.libreoffice.org/c/online/+/90792 Tested-by: Henry Castro <hcas...@collabora.com> Reviewed-by: Tamás Zolnai <tamas.zol...@collabora.com> Reviewed-by: Henry Castro <hcas...@collabora.com> diff --git a/loleaflet/src/control/Control.Toolbar.js b/loleaflet/src/control/Control.Toolbar.js index 13e8e369c..2621063e3 100644 --- a/loleaflet/src/control/Control.Toolbar.js +++ b/loleaflet/src/control/Control.Toolbar.js @@ -1010,25 +1010,6 @@ function initNormalToolbar() { hideTooltip(this, e.target); }, onRefresh: function(event) { - if (event.target === 'editbar' && map.getDocType() === 'presentation') { - // Fill the style select box if not yet filled - if ($('.styles-select')[0] && $('.styles-select')[0].length === 1) { - var data = ['']; - // Inserts a separator element - data = data.concat({text: '\u2500\u2500\u2500\u2500\u2500\u2500', disabled: true}); - - L.Styles.impressLayout.forEach(function(layout) { - data = data.concat({id: layout.id, text: _(layout.text)}); - }, this); - - $('.styles-select').select2({ - data: data, - placeholder: _UNO('.uno:LayoutStatus', 'presentation') - }); - $('.styles-select').on('select2:select', onStyleSelect); - } - } - if ((event.target === 'styles' || event.target === 'fonts' || event.target === 'fontsizes') && event.item) { var toolItem = $(this.box).find('#tb_'+ this.name +'_item_'+ w2utils.escapeId(event.item.id)); if ((_inDesktopMode() && event.item.desktop == false) @@ -1594,6 +1575,7 @@ function onDocLayerInit() { var toolbarUp = w2ui['editbar']; var statusbar = w2ui['actionbar']; var docType = map.getDocType(); + var data; switch (docType) { case 'spreadsheet': @@ -1697,6 +1679,23 @@ function onDocLayerInit() { break; case 'presentation': + // Fill the style select box if not yet filled + if ($('.styles-select')[0] && $('.styles-select')[0].length === 1) { + data = ['']; + // Inserts a separator element + data = data.concat({text: '\u2500\u2500\u2500\u2500\u2500\u2500', disabled: true}); + + L.Styles.impressLayout.forEach(function(layout) { + data = data.concat({id: layout.id, text: _(layout.text)}); + }, this); + + $('.styles-select').select2({ + data: data, + placeholder: _UNO('.uno:LayoutStatus', 'presentation') + }); + $('.styles-select').on('select2:select', onStyleSelect); + } + if (toolbarUp) { toolbarUp.show('breaksidebar', 'modifypage'); } @@ -1794,7 +1793,7 @@ function onDocLayerInit() { el.resize(); } - var data = [6, 7, 8, 9, 10, 10.5, 11, 12, 13, 14, 15, 16, 18, 20, + data = [6, 7, 8, 9, 10, 10.5, 11, 12, 13, 14, 15, 16, 18, 20, 22, 24, 26, 28, 32, 36, 40, 44, 48, 54, 60, 66, 72, 80, 88, 96]; $('.fontsizes-select').select2({ data: data, commit db585f8c64b32ba423f5c311ddd04d39809d34b4 Author: Jan Holesovsky <ke...@collabora.com> AuthorDate: Fri Mar 20 14:29:00 2020 +0100 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Mar 20 14:38:32 2020 +0100 android: Force portrait for the moment. Change-Id: Ibd3964fb7e99264104ad46c26e6ff2e4e62b6559 Reviewed-on: https://gerrit.libreoffice.org/c/online/+/90794 Tested-by: Andras Timar <andras.ti...@collabora.com> Reviewed-by: Andras Timar <andras.ti...@collabora.com> diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 6912a0b57..ecda89bbb 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -51,6 +51,7 @@ <activity android:name="org.libreoffice.androidlib.LOActivity" android:configChanges="orientation|screenSize|screenLayout|keyboardHidden" + android:screenOrientation="sensorPortrait" android:launchMode="singleTask"> <intent-filter> <action android:name="android.intent.action.VIEW" /> commit fa6e1e36688c0df4c64db84d55e7227ff9c701ad Author: Tamás Zolnai <tamas.zol...@collabora.com> AuthorDate: Fri Mar 20 12:43:02 2020 +0100 Commit: Tamás Zolnai <tamas.zol...@collabora.com> CommitDate: Fri Mar 20 12:58:19 2020 +0100 Revert "loleaflet: move the code about initialization of the style combobox" Breaks mobile view in Impress. TypeError: Cannot read property 'length' of undefined This reverts commit 9db6f855a016891f5c0d59cab014f246752cd907. Change-Id: I839cde8bb683fc0b933da806f4a4771114eb32f5 Reviewed-on: https://gerrit.libreoffice.org/c/online/+/90785 Tested-by: Tamás Zolnai <tamas.zol...@collabora.com> Reviewed-by: Tamás Zolnai <tamas.zol...@collabora.com> diff --git a/loleaflet/src/control/Control.Toolbar.js b/loleaflet/src/control/Control.Toolbar.js index 84b76d957..13e8e369c 100644 --- a/loleaflet/src/control/Control.Toolbar.js +++ b/loleaflet/src/control/Control.Toolbar.js @@ -1010,6 +1010,25 @@ function initNormalToolbar() { hideTooltip(this, e.target); }, onRefresh: function(event) { + if (event.target === 'editbar' && map.getDocType() === 'presentation') { + // Fill the style select box if not yet filled + if ($('.styles-select')[0] && $('.styles-select')[0].length === 1) { + var data = ['']; + // Inserts a separator element + data = data.concat({text: '\u2500\u2500\u2500\u2500\u2500\u2500', disabled: true}); + + L.Styles.impressLayout.forEach(function(layout) { + data = data.concat({id: layout.id, text: _(layout.text)}); + }, this); + + $('.styles-select').select2({ + data: data, + placeholder: _UNO('.uno:LayoutStatus', 'presentation') + }); + $('.styles-select').on('select2:select', onStyleSelect); + } + } + if ((event.target === 'styles' || event.target === 'fonts' || event.target === 'fontsizes') && event.item) { var toolItem = $(this.box).find('#tb_'+ this.name +'_item_'+ w2utils.escapeId(event.item.id)); if ((_inDesktopMode() && event.item.desktop == false) @@ -1575,7 +1594,6 @@ function onDocLayerInit() { var toolbarUp = w2ui['editbar']; var statusbar = w2ui['actionbar']; var docType = map.getDocType(); - var data; switch (docType) { case 'spreadsheet': @@ -1679,23 +1697,6 @@ function onDocLayerInit() { break; case 'presentation': - if ($('.styles-select')[0].length === 1) { - // Fill the style select box if not yet filled - data = ['']; - // Inserts a separator element - data = data.concat({text: '\u2500\u2500\u2500\u2500\u2500\u2500', disabled: true}); - - L.Styles.impressLayout.forEach(function(layout) { - data = data.concat({id: layout.id, text: _(layout.text)}); - }, this); - - $('.styles-select').select2({ - data: data, - placeholder: _UNO('.uno:LayoutStatus', 'presentation') - }); - $('.styles-select').on('select2:select', onStyleSelect); - } - if (toolbarUp) { toolbarUp.show('breaksidebar', 'modifypage'); } @@ -1793,7 +1794,7 @@ function onDocLayerInit() { el.resize(); } - data = [6, 7, 8, 9, 10, 10.5, 11, 12, 13, 14, 15, 16, 18, 20, + var data = [6, 7, 8, 9, 10, 10.5, 11, 12, 13, 14, 15, 16, 18, 20, 22, 24, 26, 28, 32, 36, 40, 44, 48, 54, 60, 66, 72, 80, 88, 96]; $('.fontsizes-select').select2({ data: data, commit ec65b86390d0ebc1c9f470220862dad0cff5bb77 Author: Pedro Pinto Silva <pedro.si...@collabora.com> AuthorDate: Fri Mar 20 12:21:42 2020 +0100 Commit: Tamás Zolnai <tamas.zol...@collabora.com> CommitDate: Fri Mar 20 12:57:56 2020 +0100 Mobile: Context menus: add missing icons (hyperlink and table) Change-Id: I85fb6e54d16b7982874fa4ab885c7f2d379b96fd Reviewed-on: https://gerrit.libreoffice.org/c/online/+/90781 Tested-by: Tamás Zolnai <tamas.zol...@collabora.com> Reviewed-by: Tamás Zolnai <tamas.zol...@collabora.com> diff --git a/loleaflet/images/lc_copyhyperlinklocation.svg b/loleaflet/images/lc_copyhyperlinklocation.svg new file mode 100644 index 000000000..498f956f9 --- /dev/null +++ b/loleaflet/images/lc_copyhyperlinklocation.svg @@ -0,0 +1,17 @@ +<svg version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> + <g transform="translate(2.5,2)"> + <path d="m2 0.023438c-0.554 0-1 0.446-1 1v11c0 0.554 0.446 1 1 1h8c0.554 0 1-0.446 1-1v-11c0-0.554-0.446-1-1-1zm0 1h8v11h-8z" fill="#808080"/> + <path d="m2 1.0234h8v11h-8z" fill="#fff"/> + <rect x="3" y="6.0234" width="6" height="1" ry=".5" fill="#4d82b8"/> + <rect x="3" y="9.0234" width="6" height="1" ry=".5" fill="#4d82b8"/> + <path d="m6 3c-0.554 0-1 0.446-1 1v9.6337c0 0.554 0.446 1 1 1h8c0.554 0 1-0.446 1-1v-9.6337c0-0.554-0.446-1-1-1zm0 1h8v10.634h-8z" fill="#808080"/> + <path d="m6 4h8v10.173h-8z" fill="#fff"/> + <rect x="7" y="8" width="6" height="1" ry=".5" fill="#4d82b8"/> + <rect x="7" y="11" width="6" height="1" ry=".5" fill="#4d82b8"/> + </g> + <g fill="#4d82b8"> + <path d="m17.062 15.008c-0.85377 0.1294-1.5678 0.69391-1.9054 1.4672-0.070974 0.16255-0.11815 0.34506-0.15617 0.5307h8c-0.02609-0.12499-0.05348-0.26222-0.09367-0.37462-0.3463-0.96656-1.2687-1.6233-2.3426-1.6233h-3.1273c-0.1269 0-0.25286-0.01848-0.37482 0zm-2.0615 5.9938c0.2352 1.1308 1.2393 1.9979 2.4363 1.9979h3.1273c1.197 0 2.2012-0.86716 2.4363-1.9979z"/> + <path d="m7.0615 15.008c-0.85377 0.1294-1.5678 0.69391-1.9054 1.4672-0.070974 0.16255-0.11815 0.34506-0.15617 0.5307h8c-0.02609-0.12499-0.05348-0.26222-0.09367-0.37462-0.3463-0.96656-1.2687-1.6233-2.3426-1.6233h-3.1273c-0.1269 0-0.25286-0.01848-0.37482 0zm-2.0615 5.9938c0.2352 1.1308 1.2393 1.9979 2.4363 1.9979h3.1273c1.197 0 2.2012-0.86716 2.4363-1.9979z"/> + <rect x="10" y="18" width="8" height="2" ry=".98861"/> + </g> +</svg> diff --git a/loleaflet/images/lc_openhyperlinkoncursor.svg b/loleaflet/images/lc_openhyperlinkoncursor.svg new file mode 100644 index 000000000..5ac4f48ff --- /dev/null +++ b/loleaflet/images/lc_openhyperlinkoncursor.svg @@ -0,0 +1,15 @@ +<svg version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> + <path d="m10.5 3a7.5 7.5 0 0 0-7.5 7.5 7.5 7.5 0 0 0 0.87305 3.5h13.256a7.5 7.5 0 0 0 0.87109-3.5 7.5 7.5 0 0 0-7.5-7.5z" fill="#fff"/> + <g fill="#808080"> + <path d="m10.5 2a8.5 8.5 0 0 0-8.5 8.5 8.5 8.5 0 0 0 0.76367 3.5h1.1094a7.5 7.5 0 0 1-0.87305-3.5 7.5 7.5 0 0 1 7.5-7.5 7.5 7.5 0 0 1 7.5 7.5 7.5 7.5 0 0 1-0.87109 3.5h1.1055a8.5 8.5 0 0 0 0.76562-3.5 8.5 8.5 0 0 0-8.5-8.5z"/> + <path d="m2.6376 10h15.846v1h-15.846z"/> + <path d="m9.541 2.2129c-2.4585 3.9164-3.079 7.8785-2.0781 11.787h1.0156c-1.0472-3.73-0.48226-7.4446 1.9102-11.256z" fill-rule="evenodd"/> + <path d="m11.459 2.2129-0.84766 0.53125c2.3924 3.8112 2.9574 7.5259 1.9102 11.256h1.0156c1.0008-3.9086 0.38034-7.8707-2.0781-11.787z" fill-rule="evenodd"/> + </g> + <path d="m4 5.5018c4.2087 1.1064 8.4923 1.5417 13 0" fill="none" stroke="#808080"/> + <g fill="#4d82b8"> + <path d="m17.062 15.008c-0.85377 0.1294-1.5678 0.69391-1.9054 1.4672-0.070974 0.16255-0.11815 0.34506-0.15617 0.5307h8c-0.02609-0.12499-0.05348-0.26222-0.09367-0.37462-0.3463-0.96656-1.2687-1.6233-2.3426-1.6233h-3.1273c-0.1269 0-0.25286-0.01848-0.37482 0zm-2.0615 5.9938c0.2352 1.1308 1.2393 1.9979 2.4363 1.9979h3.1273c1.197 0 2.2012-0.86716 2.4363-1.9979z"/> + <path d="m7.0615 15.008c-0.85377 0.1294-1.5678 0.69391-1.9054 1.4672-0.070974 0.16255-0.11815 0.34506-0.15617 0.5307h8c-0.02609-0.12499-0.05348-0.26222-0.09367-0.37462-0.3463-0.96656-1.2687-1.6233-2.3426-1.6233h-3.1273c-0.1269 0-0.25286-0.01848-0.37482 0zm-2.0615 5.9938c0.2352 1.1308 1.2393 1.9979 2.4363 1.9979h3.1273c1.197 0 2.2012-0.86716 2.4363-1.9979z"/> + <rect x="10" y="18" width="8" height="2" ry=".98861"/> + </g> +</svg> diff --git a/loleaflet/images/lc_removehyperlink.svg b/loleaflet/images/lc_removehyperlink.svg new file mode 100644 index 000000000..c15f04872 --- /dev/null +++ b/loleaflet/images/lc_removehyperlink.svg @@ -0,0 +1 @@ +<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m10.5 3a7.5 7.5 0 0 0 -7.5 7.5 7.5 7.5 0 0 0 7.5 7.5 7.5 7.5 0 0 0 3.294922-.767578l-1.511719-1.552734a1.0001 1.0001 0 0 1 .029297-1.423829l2.089844-1.980468a1.0001 1.0001 0 0 1 .675781-.275391 1.0001 1.0001 0 0 1 .720703.294922l1.449219 1.457031a7.5 7.5 0 0 0 .751953-3.251953 7.5 7.5 0 0 0 -7.5-7.5zm4.568359 11.398438-.644531.611328 1.056641 1.085937a7.5 7.5 0 0 0 .640625-.640625z" fill="#fff"/><path d="m10.5 2a8.5 8.5 0 0 0 -7.734375 5h-.0019531a8.5 8.5 0 0 0 -.0839844.1914062 8.5 8.5 0 0 0 -.0019531.0039063 8.5 8.5 0 0 0 -.2617188.7128906 8.5 8.5 0 0 0 -.0390625.1191407 8.5 8.5 0 0 0 -.1699219.6582031 8.5 8.5 0 0 0 -.0410156.1816406 8.5 8.5 0 0 0 -.0917968.5976563 8.5 8.5 0 0 0 -.0332032.2792968 8.5 8.5 0 0 0 -.0410156.7558594 8.5 8.5 0 0 0 .0410156.755859 8.5 8.5 0 0 0 .0332032.279297 8.5 8.5 0 0 0 .0917968.597656 8.5 8.5 0 0 0 .0410156.181641 8.5 8.5 0 0 0 .1699219.658203 8.5 8.5 0 0 0 .0390625.119141 8.5 8.5 0 0 0 .2617188.712891 8.5 8.5 0 0 0 .0019531.003906 8.5 8.5 0 0 0 .0839844.191406h.0019531a8.5 8.5 0 0 0 7.734375 5 8.5 8.5 0 0 0 2.791016-.484375l.533203-.539063-.515625-.529296a7.5 7.5 0 0 1 -1.316406.402343c.213153-.385908.400167-.77163.578124-1.158203l-.644531-.662109c-.038322-.039314-.066475-.084733-.099609-.126953-.30079.692115-.639641 1.386034-1.0625 2.083984a7.5 7.5 0 0 1 -.263672.013672 7.5 7.5 0 0 1 -.261719-.009766c-.5871482-.968625-1.0554433-1.930371-1.410156-2.888672.5564292-.054563 1.1137625-.08629 1.673828-.095703.33318-.005599.668981.012757 1.003906.023438-.010844-.344928.097725-.688607.322266-.972656-1.119279-.052177-2.2301977-.036936-3.3261719.080078-.0132955-.045676-.0184772-.091055-.03125-.136719h.0078125c-.2808166-1.000216-.4356577-1.99983-.4804687-3h5.0039061c-.032909.734534-.148724 1.468732-.306641 2.203125l1.181641-1.121094c.042532-.36051.088863-.72088.103516-1.082031h3.99414a7.5 7.5 0 0 1 -.076171.662109 7.5 7.5 0 0 1 -.033204.191407 7.5 7.5 0 0 1 -.111328.5 01953 7.5 7.5 0 0 1 -.076172.277343 7.5 7.5 0 0 1 -.128906.396485 7.5 7.5 0 0 1 -.08789.226562l.542968.546875.523438-.535156a8.5 8.5 0 0 0 .019531-.05664 8.5 8.5 0 0 0 .208984-.732422 8.5 8.5 0 0 0 .03125-.123047 8.5 8.5 0 0 0 .107422-.587891 8.5 8.5 0 0 0 .042969-.292969 8.5 8.5 0 0 0 .039062-.501953 8.5 8.5 0 0 0 .023438-.472656 8.5 8.5 0 0 0 -.023438-.472656 8.5 8.5 0 0 0 -.039062-.5019534 8.5 8.5 0 0 0 -.042969-.2929687 8.5 8.5 0 0 0 -.107422-.5878907 8.5 8.5 0 0 0 -.03125-.1230468 8.5 8.5 0 0 0 -.208984-.7324219 8.5 8.5 0 0 0 -8.046875-5.7890625zm0 1a7.5 7.5 0 0 1 .263672.0136719c.597199.9857061 1.069222 1.9644003 1.425781 2.9394531-1.129272.075053-2.2484697.0544359-3.361328-.0546875.3547127-.958301.8230078-1.9200466 1.410156-2.8886719a7.5 7.5 0 0 1 .261719-.0097656zm1.492188.1503906a7.5 7.5 0 0 1 3.910156 2.1503906c-.909879.2573893-1.811468.4436861-2.705078.5527344-.308001-.9021846-.708295-1.8037121-1.205078-2.703125zm-2.9882818.0078125c-.479075.8683005-.8688159 1.7384591-1.17 1875 2.609375-.8995078-.1257469-1.7961256-.2959431-2.6914062-.5058593a7.5 7.5 0 0 1 3.8632812-2.1035157zm-4.5820312 2.9609375c1.0222489.2536433 2.0533551.4635562 3.09375.6152344-.1596969.5820098-.2710145 1.1639579-.3515625 1.7460938-.0753467.5059078-.1239541 1.0123644-.1445313 1.5195312h-3.9941406a7.5 7.5 0 0 1 .0722656-.609375 7.5 7.5 0 0 1 .0214844-.1660156 7.5 7.5 0 0 1 .1132813-.5097656 7.5 7.5 0 0 1 .0566406-.2382813 7.5 7.5 0 0 1 .1621094-.4960937 7.5 7.5 0 0 1 .0722656-.2050782 7.5 7.5 0 0 1 .2070313-.4707031 7.5 7.5 0 0 1 .1035156-.2207031 7.5 7.5 0 0 1 .5878906-.9648438zm12.169922.0214844a7.5 7.5 0 0 1 .6875 1.1777344 7.5 7.5 0 0 1 .126953.2871094 7.5 7.5 0 0 1 .142578.3652343 7.5 7.5 0 0 1 .128906.3964844 7.5 7.5 0 0 1 .076172.2773437 7.5 7.5 0 0 1 .111328.5019532 7.5 7.5 0 0 1 .033204.1914062 7.5 7.5 0 0 1 .076171.6621094h-3.99414c-.020603-.5077947-.069024-1.0149517-.144531-1.5214844-.077549-.5515257-.18445-1.1028313-.332032-1.6542968 1.01822-.1339905 2.048215-.3640047 3. 087891-.6835938zm-8.0898439.7226562c1.321553.141098 2.6602839.166512 4.0156249.0585938.007474.0261041.010279.052025.017578.078125h-.013672c.280816 1.000216.435659 1.99983.480469 3h-5.0039061c.044811-1.00017.1996521-1.999784.4804687-3h-.0078125c.0127728-.0456638.0179545-.0910426.03125-.1367188zm-5.4765625 4.1367188h3.9941406c.0205772.507167.0691846 1.013623.1445313 1.519531.080548.582136.1918656 1.164084.3515625 1.746094-1.0403949.151678-2.0715011.361591-3.09375.615234a7.5 7.5 0 0 1 -.5878906-.964843 7.5 7.5 0 0 1 -.1035156-.220704 7.5 7.5 0 0 1 -.2070313-.470703 7.5 7.5 0 0 1 -.0722656-.205078 7.5 7.5 0 0 1 -.1621094-.496093 7.5 7.5 0 0 1 -.0566406-.238282 7.5 7.5 0 0 1 -.1132813-.509765 7.5 7.5 0 0 1 -.0214844-.166016 7.5 7.5 0 0 1 -.0722656-.609375zm4.8066406 4.232422c.3030591.870916.6928 1.741062 1.171875 2.609375a7.5 7.5 0 0 1 -3.8632812-2.103516c.8952806-.209916 1.7918984-.380112 2.6914062-.505859z" fill="#808080"/><path d="m23 20.944338-2.895447-2.946153 2.7861-3.086414-2.0083 92-1.911762-2.867282 2.941173-2.925406-2.941173-2.089573 1.98208 2.925403 3.003525-2.925403 2.955562 2.089573 2.058824 2.922011-3.088233 2.928799 3.088233z" fill="#e68497"/></svg> \ No newline at end of file diff --git a/loleaflet/images/lc_tabledeletemenu.svg b/loleaflet/images/lc_tabledeletemenu.svg new file mode 100644 index 000000000..3d0a60e08 --- /dev/null +++ b/loleaflet/images/lc_tabledeletemenu.svg @@ -0,0 +1,9 @@ +<svg version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> + <path d="m3 3h18v18h-18z" fill="#fff"/> + <path d="m3 2c-0.554 0-1 0.446-1 1v18c0 0.554 0.446 1 1 1h8.8379l0.24609-1h-3.084v-3h4.3926l1.002-1h-5.3945v-3h6v2.3945l1-0.99805v-1.3965h1.3984l0.90625-0.90625 0.007812 0.007812 0.10156-0.10156h-2.4141v-3h5v1.2715c0.36248 0.036636 0.72126 0.18842 0.99805 0.46484l0.001953 0.001953v-8.7383c0-0.554-0.446-1-1-1h-18zm0 1h5v6h-5v-6zm6 0h6v6h-6v-6zm7 0h5v6h-5v-6zm-13 7h5v3h-5v-3zm6 0h6v3h-6v-3zm-6 4h5v3h-5v-3zm19 3.1465-0.61719 0.5918-0.003906-0.003906-0.37891 0.38086v2.8848h-2.8672l-0.99414 1h3.8613c0.554 0 1-0.446 1-1v-3.8535zm-19 0.85352h5v3h-5v-3z" fill="#808080"/> + <path d="m2 2h20v4h-20z" fill="#e68497"/> + <g transform="matrix(.86194 0 0 .86196 3.2376 2.775)" fill="#e68497" stroke-width="1.3536"> + <path d="m17.48 13.611-5.5293 5.5234-0.95117 3.8652 3.8066-1.0117 5.5098-5.5449z"/> + <path d="m20.42 11c-0.18956 0-0.37818 0.07172-0.52344 0.2168l-1.6738 1.6699 2.8477 2.8496 1.7109-1.6387c0.29051-0.29013 0.29051-0.75675 0-1.0469l-1.8359-1.834c-0.14525-0.14506-0.33583-0.2168-0.52539-0.2168z"/> + </g> +</svg> diff --git a/loleaflet/images/lc_tableinsertmenu.svg b/loleaflet/images/lc_tableinsertmenu.svg new file mode 100644 index 000000000..816ea70bd --- /dev/null +++ b/loleaflet/images/lc_tableinsertmenu.svg @@ -0,0 +1,9 @@ +<svg version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> + <path d="m3 3h18v18h-18z" fill="#fff"/> + <path d="m3 2c-0.554 0-1 0.446-1 1v18c0 0.554 0.446 1 1 1h8.8379l0.24609-1h-3.084v-3h4.3926l1.002-1h-5.3945v-3h6v2.3945l1-0.99805v-1.3965h1.3984l0.19922-0.19922 0.70703-0.70703 0.007812 0.007812 0.10156-0.10156h-2.4141v-3h5v1.2715c0.36248 0.036636 0.72126 0.18842 0.99805 0.46484l0.001953 0.001953v-8.7383c0-0.554-0.446-1-1-1h-18zm0 1h5v6h-5v-6zm6 0h6v6h-6v-6zm7 0h5v6h-5v-6zm-13 7h5v3h-5v-3zm6 0h6v3h-6v-3zm-6 4h5v3h-5v-3zm19 3.1465-0.61719 0.5918-0.003906-0.003906-0.37891 0.38086v2.8848h-2.8672l-0.99414 1h3.8613c0.554 0 1-0.446 1-1v-3.8535zm-19 0.85352h5v3h-5v-3z" fill="#808080"/> + <path d="m2 2h20v4h-20z" fill="#4d82b8"/> + <g transform="matrix(.86194 0 0 .86196 3.2376 2.775)" fill="#eac282" stroke-width="1.3536"> + <path d="m17.48 13.611-5.5293 5.5234-0.95117 3.8652 3.8066-1.0117 5.5098-5.5449z"/> + <path d="m20.42 11c-0.18956 0-0.37818 0.07172-0.52344 0.2168l-1.6738 1.6699 2.8477 2.8496 1.7109-1.6387c0.29051-0.29013 0.29051-0.75675 0-1.0469l-1.8359-1.834c-0.14525-0.14506-0.33583-0.2168-0.52539-0.2168z"/> + </g> +</svg> commit 49e97638b2d3b0548440947508862720e22105f2 Author: Tamás Zolnai <tamas.zol...@collabora.com> AuthorDate: Fri Mar 20 11:51:26 2020 +0100 Commit: Tamás Zolnai <tamas.zol...@collabora.com> CommitDate: Fri Mar 20 12:45:23 2020 +0100 cypress: update README. Change-Id: Iacf4bd05b01f0ceb4f6d976f979db62afaba091b Reviewed-on: https://gerrit.libreoffice.org/c/online/+/90784 Tested-by: Tamás Zolnai <tamas.zol...@collabora.com> Reviewed-by: Tamás Zolnai <tamas.zol...@collabora.com> diff --git a/cypress_test/README b/cypress_test/README index d57fc0fc8..51d701d11 100644 --- a/cypress_test/README +++ b/cypress_test/README @@ -25,6 +25,12 @@ cypress_test folder to run cypress tests only. make check +IMPORTANT: Before stepping under cypress_test folder +and running any command listed here, make sure you've +done a top-level make, so everything is up-to-date. +Running commands from under cypress_test folder won't +trigger a rebuild. + To run cypress test cases selectively, you need to go in to the cypress_test folder first and run one of the following commands. @@ -39,13 +45,13 @@ To run all mobile tests: To run one specific test suit of desktop tests, use spec argument with a relative path to -cypress_test/data/desktop/: +cypress_test/integration_tests/desktop/: make check-desktop spec=example_desktop_test_spec.js To run one specific test suit of mobile tests, use spec argument with a relative path to -cypress_test/data/mobile/: +cypress_test/integration_tests/mobile/: make check-mobile spec=writer/toolbar_spec.js _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits