[Libreoffice-commits] online.git: Branch 'feature/proxyhack' - 17 commits - android/app cypress_test/README ios/Mobile loleaflet/html loleaflet/images loleaflet/js loleaflet/src Makefile.am net/Socket.cpp net/Socket.hpp wsd/ClientSession.hpp wsd/DocumentBroker.hpp wsd/FileServer.cpp wsd/LOOLWSD.cpp wsd/ProxyProtocol.cpp wsd/ProxyProtocol.hpp
Michael Meeks (via logerrit)
logerrit at kemper.freedesktop.org
Sat Mar 21 15:07:39 UTC 2020
Rebased ref, commits from common ancestor:
commit 634e04aeebcaaf4fe61a684a05e0f3f682234e22
Author: Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Sat Mar 21 15:07:10 2020 +0000
Commit: Michael Meeks <michael.meeks at 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.meeks at collabora.com>
AuthorDate: Sat Mar 21 14:27:15 2020 +0000
Commit: Michael Meeks <michael.meeks at 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.meeks at collabora.com>
AuthorDate: Sat Mar 21 14:19:49 2020 +0000
Commit: Michael Meeks <michael.meeks at 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.meeks at collabora.com>
AuthorDate: Fri Mar 20 20:45:38 2020 +0000
Commit: Michael Meeks <michael.meeks at 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.meeks at collabora.com>
AuthorDate: Fri Mar 20 20:15:08 2020 +0000
Commit: Michael Meeks <michael.meeks at 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.meeks at collabora.com>
AuthorDate: Fri Mar 20 19:05:48 2020 +0000
Commit: Michael Meeks <michael.meeks at 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.meeks at collabora.com>
AuthorDate: Fri Mar 20 16:38:14 2020 +0000
Commit: Michael Meeks <michael.meeks at 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.meeks at collabora.com>
AuthorDate: Thu Mar 19 15:54:28 2020 +0000
Commit: Michael Meeks <michael.meeks at 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.meeks at collabora.com>
AuthorDate: Wed Mar 4 13:54:04 2020 +0000
Commit: Michael Meeks <michael.meeks at 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.meeks at collabora.com>
AuthorDate: Wed Mar 4 13:52:51 2020 +0000
Commit: Michael Meeks <michael.meeks at 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 <vmiklos at collabora.com>
AuthorDate: Fri Mar 20 18:04:29 2020 +0100
Commit: Andras Timar <andras.timar at 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.timar at collabora.com>
Reviewed-by: Andras Timar <andras.timar at 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 <tml at collabora.com>
AuthorDate: Fri Mar 20 16:02:08 2020 +0200
Commit: Tor Lillqvist <tml at 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 <hcastro at collabora.com>
AuthorDate: Fri Mar 20 08:37:21 2020 -0400
Commit: Henry Castro <hcastro at 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 <hcastro at collabora.com>
Reviewed-by: Tamás Zolnai <tamas.zolnai at collabora.com>
Reviewed-by: Henry Castro <hcastro at 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 <kendy at collabora.com>
AuthorDate: Fri Mar 20 14:29:00 2020 +0100
Commit: Andras Timar <andras.timar at 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.timar at collabora.com>
Reviewed-by: Andras Timar <andras.timar at 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.zolnai at collabora.com>
AuthorDate: Fri Mar 20 12:43:02 2020 +0100
Commit: Tamás Zolnai <tamas.zolnai at 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.zolnai at collabora.com>
Reviewed-by: Tamás Zolnai <tamas.zolnai at 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.silva at collabora.com>
AuthorDate: Fri Mar 20 12:21:42 2020 +0100
Commit: Tamás Zolnai <tamas.zolnai at 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.zolnai at collabora.com>
Reviewed-by: Tamás Zolnai <tamas.zolnai at 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.zolnai at collabora.com>
AuthorDate: Fri Mar 20 11:51:26 2020 +0100
Commit: Tamás Zolnai <tamas.zolnai at 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.zolnai at collabora.com>
Reviewed-by: Tamás Zolnai <tamas.zolnai at 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
More information about the Libreoffice-commits
mailing list