[Libreoffice-commits] online.git: Branch 'private/mmeeks/copypaste' - 11 commits - common/Message.hpp common/Util.cpp common/Util.hpp kit/ChildSession.cpp kit/ChildSession.hpp loleaflet/src net/WebSocketHandler.hpp wsd/ClientSession.cpp wsd/ClientSession.hpp wsd/LOOLWSD.cpp wsd/LOOLWSD.hpp wsd/protocol.txt
Michael Meeks (via logerrit)
logerrit at kemper.freedesktop.org
Sat Dec 7 01:50:24 UTC 2019
Rebased ref, commits from common ancestor:
commit 2d956b4c3719699365630f16efec84e22c9a0cd0
Author: Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Wed May 29 13:11:01 2019 +0100
Commit: Michael Meeks <michael.meeks at collabora.com>
CommitDate: Wed May 29 15:10:47 2019 +0100
Add pasteresult: message to allow re-try with a different format.
Change-Id: Ia3d7aacdcf2b819437eb2da68a79dd99ed7dca42
diff --git a/kit/ChildSession.cpp b/kit/ChildSession.cpp
index 91a3ad296..f4387d60d 100644
--- a/kit/ChildSession.cpp
+++ b/kit/ChildSession.cpp
@@ -997,15 +997,19 @@ bool ChildSession::paste(const char* buffer, int length, const std::vector<std::
return false;
}
- if (mimeType.find("") == 0)
+ bool fallback = false;
+ if (mimeType.find("application/x-openoffice-embed-source-xml") == 0)
{
LOG_TRC("Re-writing garbled mime-type " << mimeType);
mimeType = "application/x-openoffice-embed-source-xml;windows_formatname=\"Star Embed Source (XML)\"";
+ fallback = true;
}
const std::string firstLine = getFirstLine(buffer, length);
const char* data = buffer + firstLine.size() + 1;
const int size = length - firstLine.size() - 1;
+ bool success = false;
+ std::string result = "pasteresult: ";
if (size > 0)
{
std::unique_lock<std::mutex> lock(_docManager.getDocumentMutex());
@@ -1013,9 +1017,17 @@ bool ChildSession::paste(const char* buffer, int length, const std::vector<std::
getLOKitDocument()->setView(_viewId);
LOG_TRC("Paste data of size " << size << " bytes and hash " << SpookyHash::Hash64(data, size, 0));
- if (!getLOKitDocument()->paste(mimeType.c_str(), data, size))
+ success = getLOKitDocument()->paste(mimeType.c_str(), data, size);
+ if (!success)
LOG_WRN("Paste failed " << getLOKitLastError());
}
+ if (success)
+ result += "success";
+ else if (fallback)
+ result += "fallback";
+ else
+ result += "fail";
+ sendTextFrame(result);
return true;
}
diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js
index b9a23067a..71705148a 100644
--- a/loleaflet/src/layer/tile/TileLayer.js
+++ b/loleaflet/src/layer/tile/TileLayer.js
@@ -493,6 +493,9 @@ L.TileLayer = L.GridLayer.extend({
else if (textMsg.startsWith('editor:')) {
this._updateEditor(textMsg);
}
+ else if (textMsg.startsWith('pasteresult:')) {
+ this._pasteResult(textMsg);
+ }
else if (textMsg.startsWith('validitylistbutton:')) {
this._onValidityListButtonMsg(textMsg);
}
@@ -2493,6 +2496,23 @@ L.TileLayer = L.GridLayer.extend({
return '';
},
+ // Sometimes our smart-paste fails & we need to try again.
+ _pasteResult : function(textMsg)
+ {
+ textMsg = textMsg.substring('pasteresult:'.length + 1);
+ console.log('Paste state: ' + textMsg);
+ if (textMsg == 'fallback') {
+ if (this._pasteFallback != null) {
+ console.log('Paste failed- falling back to HTML');
+ this._map._socket.sendMessage(this._pasteFallback);
+ } else {
+ console.log('No paste fallback present.');
+ }
+ }
+
+ this._pasteFallback = null;
+ },
+
_dataTransferToDocument: function (dataTransfer, preferInternal) {
// Look for our HTML meta magic.
@@ -2511,6 +2531,9 @@ L.TileLayer = L.GridLayer.extend({
return;
}
+ console.log('Resetting paste fallback');
+ this._pasteFallback = null;
+
// Suck HTML content out of dataTransfer now while it feels like working.
var content = this._readContentSync(dataTransfer);
@@ -2541,10 +2564,12 @@ L.TileLayer = L.GridLayer.extend({
return;
}
- // Try Fetch the data directly ourselves instead.
- if (meta != '') {
+ if (meta != '') { // in Smart server side paste mode ...
// FIXME: really should short-circuit on the server.
console.log('Doing async paste of data from remote origin\n\t"' + meta + '" is not\n\t"' + id + '"');
+
+ this._pasteFallback = content;
+
var tilelayer = this;
var oReq = new XMLHttpRequest();
oReq.onload = function(e) {
@@ -2556,18 +2581,24 @@ L.TileLayer = L.GridLayer.extend({
} else {
console.log('Error code ' + oReq.status + ' fetching from URL "' + meta + '": ' + e + ' falling back to local.');
tilelayer._map._socket.sendMessage(content);
+ this._pasteFallback = null;
}
}
oReq.onerror = function(e) {
console.log('Error fetching from URL "' + meta + '": ' + e + ' falling back to local.');
tilelayer._map._socket.sendMessage(content);
+ this._pasteFallback = null;
};
oReq.open('GET', meta);
oReq.responseType = 'arraybuffer';
oReq.send();
// user abort - if they do stops paste.
+ } else if (content != null) {
+ console.log('Normal HTML, so smart paste not possible');
+ tilelayer._map._socket.sendMessage(content);
+ this._pasteFallback = null;
} else {
- console.log('Received a paste but nothing on the clipboard');
+ console.log('Nothing we can paste on the clipboard');
}
},
@@ -2575,9 +2606,9 @@ L.TileLayer = L.GridLayer.extend({
// Try various content mime types
var mimeTypes;
if (this._docType === 'spreadsheet') {
- // FIXME apparently we cannot paste the text/html or text/rtf as
- // produced by LibreOffice in Calc from some reason
mimeTypes = [
+ ['text/rtf', 'text/rtf'],
+ ['text/html', 'text/html'],
['text/plain', 'text/plain;charset=utf-8'],
['Text', 'text/plain;charset=utf-8']
];
commit ce3f672b0675b4dfb00d9f0c4d73d0fd24850ba5
Author: Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Wed May 29 13:10:44 2019 +0100
Commit: Michael Meeks <michael.meeks at collabora.com>
CommitDate: Wed May 29 13:10:44 2019 +0100
Improve debugging by showing un-masked websocket content.
Change-Id: I0df0b3cc7b13c36ed8afaaec4ed2fe525458a21c
diff --git a/net/WebSocketHandler.hpp b/net/WebSocketHandler.hpp
index 843ca6b37..85d9a2d7f 100644
--- a/net/WebSocketHandler.hpp
+++ b/net/WebSocketHandler.hpp
@@ -351,7 +351,8 @@ public:
LOG_TRC("#" << socket->getFD() << ": Incoming WebSocket frame code " << static_cast<unsigned>(code) <<
", fin? " << fin << ", mask? " << hasMask << ", payload length: " << payloadLen <<
- ", residual socket data: " << socket->getInBuffer().size() << " bytes.");
+ ", residual socket data: " << socket->getInBuffer().size() << " bytes, unmasked data: "+
+ Util::stringifyHexLine(_wsPayload, 0, std::min((size_t)32, _wsPayload.size())));
if (fin)
{
commit 47daa9da89d7a4a4f37083bbfb6183a8bcc91ca3
Author: Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Tue May 28 11:42:40 2019 +0100
Commit: Michael Meeks <michael.meeks at collabora.com>
CommitDate: Tue May 28 11:42:40 2019 +0100
End to end hashing.
Change-Id: Iac4423114111ced3ad45bc8dd58b24b584d0941d
diff --git a/kit/ChildSession.cpp b/kit/ChildSession.cpp
index a55081396..91a3ad296 100644
--- a/kit/ChildSession.cpp
+++ b/kit/ChildSession.cpp
@@ -975,7 +975,8 @@ bool ChildSession::getBinarySelection(const char* /*buffer*/, int /*length*/, co
if (binSelection)
std::memcpy(output.data() + header.size(), binSelection, binSize);
- LOG_TRC("Sending binaryselectioncontent (" << binSize << " bytes) in: " <<
+ LOG_TRC("Sending binaryselectioncontent (" << binSize << " bytes) and hash " <<
+ SpookyHash::Hash64(binSelection, binSize, 0) << " in: " <<
mimeType << " out: " << (mimeTypeOut ? mimeTypeOut : ""));
sendBinaryFrame(output.data(), output.size());
if (binSelection)
@@ -1011,6 +1012,7 @@ bool ChildSession::paste(const char* buffer, int length, const std::vector<std::
getLOKitDocument()->setView(_viewId);
+ LOG_TRC("Paste data of size " << size << " bytes and hash " << SpookyHash::Hash64(data, size, 0));
if (!getLOKitDocument()->paste(mimeType.c_str(), data, size))
LOG_WRN("Paste failed " << getLOKitLastError());
}
commit eac30d86883d4f3d1dab622c1e2fb49b9c773601
Author: Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Tue May 28 03:58:22 2019 +0100
Commit: Michael Meeks <michael.meeks at collabora.com>
CommitDate: Tue May 28 07:20:20 2019 +0100
Async socket feeding.
Change-Id: Ifbc860593dbcd904c213862c14bc965100de7022
diff --git a/kit/ChildSession.cpp b/kit/ChildSession.cpp
index 11b552e62..a55081396 100644
--- a/kit/ChildSession.cpp
+++ b/kit/ChildSession.cpp
@@ -268,6 +268,7 @@ bool ChildSession::_handleInput(const char *buffer, int length)
tokens[0] == "downloadas" ||
tokens[0] == "getchildid" ||
tokens[0] == "gettextselection" ||
+ tokens[0] == "getbinaryselection" ||
tokens[0] == "paste" ||
tokens[0] == "insertfile" ||
tokens[0] == "key" ||
@@ -310,10 +311,14 @@ bool ChildSession::_handleInput(const char *buffer, int length)
{
return getChildId();
}
- else if (tokens[0] == "gettextselection")
+ else if (tokens[0] == "gettextselection") // deprecated.
{
return getTextSelection(buffer, length, tokens);
}
+ else if (tokens[0] == "getbinaryselection")
+ {
+ return getBinarySelection(buffer, length, tokens);
+ }
else if (tokens[0] == "paste")
{
return paste(buffer, length, tokens);
@@ -938,6 +943,49 @@ bool ChildSession::getTextSelection(const char* /*buffer*/, int /*length*/, cons
return true;
}
+bool ChildSession::getBinarySelection(const char* /*buffer*/, int /*length*/, const std::vector<std::string>& tokens)
+{
+ std::string mimeType;
+
+ if (tokens.size() != 2 ||
+ !getTokenString(tokens[1], "mimetype", mimeType))
+ {
+ sendTextFrame("error: cmd=getbinaryselection kind=syntax");
+ return false;
+ }
+
+ size_t binSize = 0;
+ char* binSelection = nullptr;
+ char *mimeTypeOut = nullptr;
+ {
+ std::unique_lock<std::mutex> lock(_docManager.getDocumentMutex());
+ getLOKitDocument()->setView(_viewId);
+
+ binSelection = getLOKitDocument()->getBinarySelection(mimeType.c_str(), &binSize, &mimeTypeOut);
+ }
+
+ std::string header("binaryselectioncontent: mimetype=");
+ if (mimeTypeOut)
+ header += mimeTypeOut;
+ header += "\n";
+
+ std::vector<char> output;
+ output.resize(header.size() + binSize);
+ std::memcpy(output.data(), header.c_str(), header.size());
+ if (binSelection)
+ std::memcpy(output.data() + header.size(), binSelection, binSize);
+
+ LOG_TRC("Sending binaryselectioncontent (" << binSize << " bytes) in: " <<
+ mimeType << " out: " << (mimeTypeOut ? mimeTypeOut : ""));
+ sendBinaryFrame(output.data(), output.size());
+ if (binSelection)
+ free(binSelection);
+ if (mimeTypeOut)
+ free (mimeTypeOut);
+
+ return true;
+}
+
bool ChildSession::paste(const char* buffer, int length, const std::vector<std::string>& tokens)
{
std::string mimeType;
@@ -948,6 +996,12 @@ bool ChildSession::paste(const char* buffer, int length, const std::vector<std::
return false;
}
+ if (mimeType.find("") == 0)
+ {
+ LOG_TRC("Re-writing garbled mime-type " << mimeType);
+ mimeType = "application/x-openoffice-embed-source-xml;windows_formatname=\"Star Embed Source (XML)\"";
+ }
+
const std::string firstLine = getFirstLine(buffer, length);
const char* data = buffer + firstLine.size() + 1;
const int size = length - firstLine.size() - 1;
@@ -957,7 +1011,8 @@ bool ChildSession::paste(const char* buffer, int length, const std::vector<std::
getLOKitDocument()->setView(_viewId);
- getLOKitDocument()->paste(mimeType.c_str(), data, size);
+ if (!getLOKitDocument()->paste(mimeType.c_str(), data, size))
+ LOG_WRN("Paste failed " << getLOKitLastError());
}
return true;
diff --git a/kit/ChildSession.hpp b/kit/ChildSession.hpp
index 8b1bc7ff1..2f8a8b2ce 100644
--- a/kit/ChildSession.hpp
+++ b/kit/ChildSession.hpp
@@ -253,6 +253,7 @@ private:
bool downloadAs(const char* buffer, int length, const std::vector<std::string>& tokens);
bool getChildId();
bool getTextSelection(const char* buffer, int length, const std::vector<std::string>& tokens);
+ bool getBinarySelection(const char* buffer, int length, const std::vector<std::string>& tokens);
std::string getTextSelectionInternal(const std::string& mimeType);
bool paste(const char* buffer, int length, const std::vector<std::string>& tokens);
bool insertFile(const char* buffer, int length, const std::vector<std::string>& tokens);
@@ -289,6 +290,18 @@ private:
return _docManager.getLOKitDocument();
}
+ std::string getLOKitLastError()
+ {
+ char *lastErr = _docManager.getLOKit()->getError();
+ std::string ret;
+ if (lastErr)
+ {
+ ret = std::string(lastErr, strlen(lastErr));
+ free (lastErr);
+ }
+ return ret;
+ }
+
private:
const std::string _jailId;
DocumentManagerInterface& _docManager;
diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js
index e3d6cd7b5..b9a23067a 100644
--- a/loleaflet/src/layer/tile/TileLayer.js
+++ b/loleaflet/src/layer/tile/TileLayer.js
@@ -2550,10 +2550,9 @@ L.TileLayer = L.GridLayer.extend({
oReq.onload = function(e) {
var arraybuffer = oReq.response;
if (oReq.status == 200) { // OK
-// var blob = new Blob(['paste mimetype=application/x-openoffice-embed-source-xml;windows_formatname="Star Embed Source (XML)"\n', arraybuffer]);
- var blob = new Blob(['paste mimetype=text/plain\n', arraybuffer]);
+ var blob = new Blob(['paste mimetype=application/x-openoffice-embed-source-xml;windows_formatname="Star Embed Source (XML)"\n', arraybuffer]);
tilelayer._map._socket.sendMessage(blob);
- console.log('Sent paste blob message');
+ console.log('Sent paste blob message of size ' + arraybuffer.byteLength);
} else {
console.log('Error code ' + oReq.status + ' fetching from URL "' + meta + '": ' + e + ' falling back to local.');
tilelayer._map._socket.sendMessage(content);
diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp
index 738521d43..b393e4d0c 100644
--- a/wsd/ClientSession.cpp
+++ b/wsd/ClientSession.cpp
@@ -87,7 +87,8 @@ std::string ClientSession::getURIAndUpdateClipboardHash()
{
std::unique_lock<std::mutex> lock(SessionMapMutex);
_clipboardKey = hash;
- }
+ LOG_TRC("Clipboard key on [" << getId() << "] set to " << _clipboardKey);
+ }
std::string encodedFrom;
Poco::URI wopiSrc = getDocumentBroker()->getPublicUri();
@@ -117,11 +118,34 @@ std::shared_ptr<ClientSession> ClientSession::getByClipboardHash(std::string &ke
{
auto session = it.second.lock();
if (session && session->_clipboardKey == key)
- return session;
+ {
+ if (session->_wopiFileInfo &&
+ session->_wopiFileInfo->getDisableCopy())
+ {
+ LOG_WRN("Attempting copy from disabled session " << key);
+ break;
+ }
+ else
+ return session;
+ }
}
return std::shared_ptr<ClientSession>();
}
+void ClientSession::addClipboardSocket(const std::shared_ptr<StreamSocket> &socket)
+{
+ // Move the socket into our DocBroker.
+ auto docBroker = getDocumentBroker();
+ docBroker->addSocketToPoll(socket);
+
+ LOG_TRC("Session [" << getId() << "] sending getbinaryselection");
+ const std::string gettext = "getbinaryselection mimetype=application/x-openoffice-embed-source-xml";
+ docBroker->forwardToChild(getId(), gettext);
+
+ // TESTME: onerror / socket cleanup.
+ _clipSockets.push_back(socket);
+}
+
void ClientSession::handleIncomingMessage(SocketDisposition &disposition)
{
// LOG_TRC("***** ClientSession::handleIncomingMessage()");
@@ -1036,6 +1060,7 @@ bool ClientSession::handleKitToClientMessage(const char* buffer, const int lengt
}
}
} else if (tokens[0] == "textselectioncontent:") {
+
// Insert our meta origin if we can
payload->rewriteDataBody([=](std::vector<char>& data) {
size_t pos = Util::findInVector(data, "<meta name=\"generator\" content=\"");
@@ -1055,6 +1080,39 @@ bool ClientSession::handleKitToClientMessage(const char* buffer, const int lengt
}
});
return forwardToClient(payload);
+
+ } else if (tokens[0] == "binaryselectioncontent:") {
+
+ // for now just for remote sockets.
+ LOG_TRC("Got binary selection content to send to " << _clipSockets.size() << "sockets");
+ size_t header;
+ for (header = 0; header < payload->size();)
+ if (payload->data()[header++] == '\n')
+ break;
+ if (header < payload->size())
+ {
+ for (auto it : _clipSockets)
+ {
+ std::ostringstream oss;
+ oss << "HTTP/1.1 200 OK\r\n"
+ << "Last-Modified: " << Poco::DateTimeFormatter::format(Poco::Timestamp(), Poco::DateTimeFormat::HTTP_FORMAT) << "\r\n"
+ << "User-Agent: " << WOPI_AGENT_STRING << "\r\n"
+ << "Content-Length: " << (payload->size() - header) << "\r\n"
+ << "Content-Type: application/octet-stream\r\n"
+ << "X-Content-Type-Options: nosniff\r\n"
+ << "\r\n";
+ oss.write(&payload->data()[header], payload->size() - header);
+ auto socket = it.lock();
+ socket->setSocketBufferSize(std::min(payload->size() + 256,
+ size_t(Socket::MaximumSendBufferSize)));
+ socket->send(oss.str());
+ socket->shutdown();
+ LOG_INF("Sent clipboard content.");
+ }
+ }
+ else
+ LOG_DBG("Odd - no binary selection content");
+ _clipSockets.clear();
}
if (!isDocPasswordProtected())
@@ -1372,7 +1430,8 @@ void ClientSession::dumpState(std::ostream& os)
<< "\n\t\tkit ViewId: " << _kitViewId
<< "\n\t\thost (un-trusted): " << _hostNoTrust
<< "\n\t\tisTextDocument: " << _isTextDocument
- << "\n\t\tclipboardKey: " << _clipboardKey;
+ << "\n\t\tclipboardKey: " << _clipboardKey
+ << "\n\t\tclip sockets: " << _clipSockets.size();
std::shared_ptr<StreamSocket> socket = getSocket().lock();
if (socket)
diff --git a/wsd/ClientSession.hpp b/wsd/ClientSession.hpp
index 61b3c037c..f8993f825 100644
--- a/wsd/ClientSession.hpp
+++ b/wsd/ClientSession.hpp
@@ -140,6 +140,8 @@ public:
/// Find clipboard for session
static std::shared_ptr<ClientSession> getByClipboardHash(std::string &key);
+ void addClipboardSocket(const std::shared_ptr<StreamSocket> &socket);
+
private:
/// Create URI for transient clipboard content.
@@ -249,6 +251,9 @@ private:
/// Store wireID's of the sent tiles inside the actual visible area
std::map<std::string, TileWireId> _oldWireIds;
+
+ /// Sockets to send binary selection content to
+ std::vector<std::weak_ptr<StreamSocket>> _clipSockets;
};
diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp
index c4048b468..599a6f2c0 100644
--- a/wsd/LOOLWSD.cpp
+++ b/wsd/LOOLWSD.cpp
@@ -2158,7 +2158,7 @@ private:
if (request.getMethod() == HTTPRequest::HTTP_GET &&
reqPathTokens.count() > 0 && reqPathTokens[0] == "clipboard")
{
- handleClipboardRequest(request);
+ handleClipboardRequest(request, disposition);
}
else if (!(request.find("Upgrade") != request.end() && Poco::icompare(request["Upgrade"], "websocket") == 0) &&
reqPathTokens.count() > 0 && reqPathTokens[0] == "lool")
@@ -2324,7 +2324,7 @@ private:
LOG_INF("Sent capabilities.json successfully.");
}
- void handleClipboardRequest(const Poco::Net::HTTPRequest& request)
+ void handleClipboardRequest(const Poco::Net::HTTPRequest& request, SocketDisposition &disposition)
{
LOG_DBG("Clipboard request: " << request.getURI());
@@ -2349,26 +2349,23 @@ private:
{
// Do things in the right thread.
auto docBroker = session->getDocumentBroker();
- docBroker->addCallback([=](){
- ;
- });
- std::string result = "Hello world !\n";
+ disposition.setMove([docBroker, session]
+ (const std::shared_ptr<Socket> &moveSocket)
+ {
+ // Perform all of this after removing the socket
- std::ostringstream oss;
- oss << "HTTP/1.1 200 OK\r\n"
- << "Last-Modified: " << Poco::DateTimeFormatter::format(Poco::Timestamp(), Poco::DateTimeFormat::HTTP_FORMAT) << "\r\n"
- << "User-Agent: " << WOPI_AGENT_STRING << "\r\n"
- << "Content-Length: " << result.size() << "\r\n"
- << "Content-Type: application/octet-stream\r\n"
- << "X-Content-Type-Options: nosniff\r\n"
- << "\r\n"
- << result;
+ // We no longer own this socket.
+ moveSocket->setThreadOwner(std::thread::id(0));
+
+ docBroker->addCallback([docBroker, moveSocket, session]()
+ {
+ auto streamSocket = std::static_pointer_cast<StreamSocket>(moveSocket);
+ session->addClipboardSocket(streamSocket);
+ });
+ });
+ LOG_TRC("queued binary clipboard content fetch");
- auto socket = _socket.lock();
- socket->send(oss.str());
- socket->shutdown();
- LOG_INF("Sent clipboard content successfully.");
} else {
LOG_ERR("Invalid clipboard request: " << serverId << " with tag " << tag);
diff --git a/wsd/protocol.txt b/wsd/protocol.txt
index 865d3fd1b..a03969391 100644
--- a/wsd/protocol.txt
+++ b/wsd/protocol.txt
@@ -53,6 +53,10 @@ gettextselection mimetype=<mimeType>
Request selection's content
+getbinaryselection mimetype=<mimeType>
+
+ Request selection's content
+
paste mimetype=<mimeType>
<binaryPasteData>
@@ -390,10 +394,12 @@ statechanged: <key>=<value>
Notifies client of state changed events of <key>.
Eg: 'statechanged: .uno:Undo=enabled'
-textselectioncontent: <content>
-
+textselectioncontent:
Current selection's content
+binaryselectioncontent mimetype=<type>
+<binary selection content>
+
tile: part=<partNumber> width=<width> height=<height> tileposx=<xpos> tileposy=<ypos> tilewidth=<tileWidth> tileheight=<tileHeight> [timestamp=<time>] [renderid=<id>] [wid=<wireId>]
<binaryPngImage>
commit c2067a9174748285736549c45276ac741a0fcda6
Author: Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Tue May 28 02:00:46 2019 +0100
Commit: Michael Meeks <michael.meeks at collabora.com>
CommitDate: Tue May 28 02:26:47 2019 +0100
Start of clipboard server end-point ...
Change-Id: I6da502cc1aacef450d612696abcc38f8d8d8873a
diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js
index 697ab6040..e3d6cd7b5 100644
--- a/loleaflet/src/layer/tile/TileLayer.js
+++ b/loleaflet/src/layer/tile/TileLayer.js
@@ -2483,9 +2483,11 @@ L.TileLayer = L.GridLayer.extend({
var meta = html.substring(start + match.length, end);
// quick sanity checks that it one of ours.
- if (meta.indexOf('/clipboard/') > 0 &&
- meta.indexOf('&tag=') > 0)
- return meta;
+ if (meta.indexOf('%2Fclipboard%3FWOPISrc%3D') > 0 &&
+ meta.indexOf('%26ServerId%3D') > 0 &&
+ meta.indexOf('%26ViewId%3D') > 0 &&
+ meta.indexOf('%26Tag%3D') > 0)
+ return decodeURIComponent(meta);
else
console.log('Mis-understood foreign origin: "' + meta + '"');
return '';
@@ -2498,8 +2500,8 @@ L.TileLayer = L.GridLayer.extend({
var pasteHtml = dataTransfer.getData('text/html');
var meta = this._getMetaOrigin(pasteHtml);
var id = this._map.options.webserver + this._map.options.serviceRoot +
- '/clipboard/' + this._map._socket.WSDServer.Id + '/' + this._viewId +
- '?WOPISrc=' + encodeURIComponent(this._map.options.doc);
+ '/clipboard?WOPISrc='+ encodeURIComponent(this._map.options.doc) +
+ '&ServerId=' + this._map._socket.WSDServer.Id + '&ViewId=' + this._viewId;
// for the paste, we might prefer the internal LOK's copy/paste
if (meta.startsWith(id) && preferInternal === true) {
@@ -2545,10 +2547,11 @@ L.TileLayer = L.GridLayer.extend({
console.log('Doing async paste of data from remote origin\n\t"' + meta + '" is not\n\t"' + id + '"');
var tilelayer = this;
var oReq = new XMLHttpRequest();
- oReq.onload = function() {
+ oReq.onload = function(e) {
var arraybuffer = oReq.response;
if (oReq.status == 200) { // OK
- var blob = new Blob(['paste mimetype=application/x-openoffice-embed-source-xml;windows_formatname="Star Embed Source (XML)"\n', arraybuffer]);
+// var blob = new Blob(['paste mimetype=application/x-openoffice-embed-source-xml;windows_formatname="Star Embed Source (XML)"\n', arraybuffer]);
+ var blob = new Blob(['paste mimetype=text/plain\n', arraybuffer]);
tilelayer._map._socket.sendMessage(blob);
console.log('Sent paste blob message');
} else {
diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp
index 899eae49f..738521d43 100644
--- a/wsd/ClientSession.cpp
+++ b/wsd/ClientSession.cpp
@@ -97,9 +97,16 @@ std::string ClientSession::getURIAndUpdateClipboardHash()
Poco::URI::encode(wopiSrc.toString(), encodeChars, encodedFrom);
std::string proto = (LOOLWSD::isSSLEnabled() || LOOLWSD::isSSLTermination()) ? "https://" : "http://";
- std::string meta = proto + _hostNoTrust + "/clipboard/" + LOOLWSD::HostIdentifier +
- "/" + std::to_string(getKitViewId()) + "?WOPISrc=" + encodedFrom + "&tag=" + hash;
- return meta;
+ std::string meta = proto + _hostNoTrust +
+ "/clipboard?WOPISrc=" + encodedFrom +
+ "&ServerId=" + LOOLWSD::HostIdentifier +
+ "&ViewId=" + std::to_string(getKitViewId()) +
+ "&Tag=" + hash;
+
+ std::string metaEncoded;
+ Poco::URI::encode(meta, encodeChars, metaEncoded);
+
+ return metaEncoded;
}
// called infrequently
@@ -1357,7 +1364,15 @@ void ClientSession::dumpState(std::ostream& os)
os << "\t\tisReadOnly: " << isReadOnly()
<< "\n\t\tisDocumentOwner: " << _isDocumentOwner
<< "\n\t\tisAttached: " << _isAttached
- << "\n\t\tkeyEvents: " << _keyEvents;
+ << "\n\t\tkeyEvents: " << _keyEvents
+// << "\n\t\tvisibleArea: " << _clientVisibleArea
+ << "\n\t\tclientSelectedPart: " << _clientSelectedPart
+ << "\n\t\ttile size Pixel: " << _tileWidthPixel << "x" << _tileHeightPixel
+ << "\n\t\ttile size Twips: " << _tileWidthTwips << "x" << _tileHeightTwips
+ << "\n\t\tkit ViewId: " << _kitViewId
+ << "\n\t\thost (un-trusted): " << _hostNoTrust
+ << "\n\t\tisTextDocument: " << _isTextDocument
+ << "\n\t\tclipboardKey: " << _clipboardKey;
std::shared_ptr<StreamSocket> socket = getSocket().lock();
if (socket)
diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp
index f9dafd373..c4048b468 100644
--- a/wsd/LOOLWSD.cpp
+++ b/wsd/LOOLWSD.cpp
@@ -2154,7 +2154,13 @@ private:
else
{
StringTokenizer reqPathTokens(request.getURI(), "/?", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
- if (!(request.find("Upgrade") != request.end() && Poco::icompare(request["Upgrade"], "websocket") == 0) &&
+
+ if (request.getMethod() == HTTPRequest::HTTP_GET &&
+ reqPathTokens.count() > 0 && reqPathTokens[0] == "clipboard")
+ {
+ handleClipboardRequest(request);
+ }
+ else if (!(request.find("Upgrade") != request.end() && Poco::icompare(request["Upgrade"], "websocket") == 0) &&
reqPathTokens.count() > 0 && reqPathTokens[0] == "lool")
{
// All post requests have url prefix 'lool'.
@@ -2318,6 +2324,74 @@ private:
LOG_INF("Sent capabilities.json successfully.");
}
+ void handleClipboardRequest(const Poco::Net::HTTPRequest& request)
+ {
+ LOG_DBG("Clipboard request: " << request.getURI());
+
+ Poco::URI requestUri(request.getURI());
+ Poco::URI::QueryParameters params = requestUri.getQueryParameters();
+ std::string serverId, viewId, tag;
+ for (auto it : params)
+ {
+ if (it.first == "ServerId")
+ serverId = it.second;
+ else if (it.first == "ViewId")
+ viewId = it.second;
+ else if (it.first == "Tag")
+ tag = it.second;
+ }
+ LOG_TRC("Clipboard request for us: " << serverId << " with tag " << tag);
+
+ // TODO: check WOPISrc too ...
+ std::shared_ptr<ClientSession> session;
+ if (serverId == LOOLWSD::HostIdentifier &&
+ (session = ClientSession::getByClipboardHash(tag)))
+ {
+ // Do things in the right thread.
+ auto docBroker = session->getDocumentBroker();
+ docBroker->addCallback([=](){
+ ;
+ });
+
+ std::string result = "Hello world !\n";
+
+ std::ostringstream oss;
+ oss << "HTTP/1.1 200 OK\r\n"
+ << "Last-Modified: " << Poco::DateTimeFormatter::format(Poco::Timestamp(), Poco::DateTimeFormat::HTTP_FORMAT) << "\r\n"
+ << "User-Agent: " << WOPI_AGENT_STRING << "\r\n"
+ << "Content-Length: " << result.size() << "\r\n"
+ << "Content-Type: application/octet-stream\r\n"
+ << "X-Content-Type-Options: nosniff\r\n"
+ << "\r\n"
+ << result;
+
+ auto socket = _socket.lock();
+ socket->send(oss.str());
+ socket->shutdown();
+ LOG_INF("Sent clipboard content successfully.");
+ } else {
+ LOG_ERR("Invalid clipboard request: " << serverId << " with tag " << tag);
+
+ std::string message;
+ if (serverId != LOOLWSD::HostIdentifier)
+ message = "Cluster configuration error: mis-matching serverid " + serverId + " vs. " + LOOLWSD::HostIdentifier;
+ else
+ message = "Empty clipboard item / session tag " + tag;
+
+ // Bad request.
+ std::ostringstream oss;
+ oss << "HTTP/1.1 400\r\n"
+ << "Date: " << Poco::DateTimeFormatter::format(Poco::Timestamp(), Poco::DateTimeFormat::HTTP_FORMAT) << "\r\n"
+ << "User-Agent: LOOLWSD WOPI Agent\r\n"
+ << "Content-Length: 0\r\n"
+ << "\r\n"
+ << message;
+ auto socket = _socket.lock();
+ socket->send(oss.str());
+ socket->shutdown();
+ }
+ }
+
void handleRobotsTxtRequest(const Poco::Net::HTTPRequest& request)
{
LOG_DBG("HTTP request: " << request.getURI());
commit d8195aae3c5008dbfaa6b411ae8f83a92092e02d
Author: Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Mon May 27 21:22:06 2019 +0100
Commit: Michael Meeks <michael.meeks at collabora.com>
CommitDate: Tue May 28 02:26:47 2019 +0100
First cut of remote clipboard access fun.
Change-Id: I02e40896753e40ad823beafe1f9da739e3023f43
diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js
index 6916ab775..697ab6040 100644
--- a/loleaflet/src/layer/tile/TileLayer.js
+++ b/loleaflet/src/layer/tile/TileLayer.js
@@ -2480,46 +2480,97 @@ L.TileLayer = L.GridLayer.extend({
if (end < 0) {
return '';
}
- return html.substring(start + match.length, end);
+ var meta = html.substring(start + match.length, end);
+
+ // quick sanity checks that it one of ours.
+ if (meta.indexOf('/clipboard/') > 0 &&
+ meta.indexOf('&tag=') > 0)
+ return meta;
+ else
+ console.log('Mis-understood foreign origin: "' + meta + '"');
+ return '';
},
_dataTransferToDocument: function (dataTransfer, preferInternal) {
+
+ // Look for our HTML meta magic.
+ // cf. ClientSession.cpp /textselectioncontent:/
+ var pasteHtml = dataTransfer.getData('text/html');
+ var meta = this._getMetaOrigin(pasteHtml);
+ var id = this._map.options.webserver + this._map.options.serviceRoot +
+ '/clipboard/' + this._map._socket.WSDServer.Id + '/' + this._viewId +
+ '?WOPISrc=' + encodeURIComponent(this._map.options.doc);
+
// for the paste, we might prefer the internal LOK's copy/paste
- if (preferInternal === true) {
- var pasteHtml = dataTransfer.getData('text/html');
- var meta = this._getMetaOrigin(pasteHtml);
- var id = 'https://transient/' + this._map._socket.WSDServer.Id + '/' + this._viewId +
- '?WOPISrc=' + encodeURIComponent(this._map.options.doc);
- // cf. ClientSession.cpp /textselectioncontent:/
- if (meta == id) {
- // Home from home: short-circuit internally.
- this._map._socket.sendMessage('uno .uno:Paste');
- return;
- } else
- console.log('Unusual origin mismatch on paste between:\n\t"' +
- meta + '" and\n\t"' + id + '"');
+ if (meta.startsWith(id) && preferInternal === true) {
+ // Home from home: short-circuit internally.
+ console.log('short-circuit, internal paste');
+ this._map._socket.sendMessage('uno .uno:Paste');
+ return;
}
- var types = dataTransfer.types;
+ // Suck HTML content out of dataTransfer now while it feels like working.
+ var content = this._readContentSync(dataTransfer);
- // first try to transfer images
- // TODO if we have both Files and a normal mimetype, should we handle
- // both, or prefer one or the other?
- for (var t = 0; t < types.length; ++t) {
- if (types[t] === 'Files') {
- var files = dataTransfer.files;
- for (var f = 0; f < files.length; ++f) {
- var file = files[f];
- if (file.type.match(/image.*/)) {
- var reader = new FileReader();
- reader.onload = this._onFileLoadFunc(file);
- reader.readAsArrayBuffer(file);
+ // Images get a look in only if we have no content and are async
+ if (content == null && pasteHtml == '')
+ {
+ var types = dataTransfer.types;
+
+ console.log('Attempting to paste image(s)');
+
+ // first try to transfer images
+ // TODO if we have both Files and a normal mimetype, should we handle
+ // both, or prefer one or the other?
+ for (var t = 0; t < types.length; ++t) {
+ console.log('\ttype' + types[t]);
+ if (types[t] === 'Files') {
+ var files = dataTransfer.files;
+ for (var f = 0; f < files.length; ++f) {
+ var file = files[f];
+ if (file.type.match(/image.*/)) {
+ var reader = new FileReader();
+ reader.onload = this._onFileLoadFunc(file);
+ reader.readAsArrayBuffer(file);
+ }
}
}
}
+ return;
}
- // now try various mime types
+ // Try Fetch the data directly ourselves instead.
+ if (meta != '') {
+ // FIXME: really should short-circuit on the server.
+ console.log('Doing async paste of data from remote origin\n\t"' + meta + '" is not\n\t"' + id + '"');
+ var tilelayer = this;
+ var oReq = new XMLHttpRequest();
+ oReq.onload = function() {
+ var arraybuffer = oReq.response;
+ if (oReq.status == 200) { // OK
+ var blob = new Blob(['paste mimetype=application/x-openoffice-embed-source-xml;windows_formatname="Star Embed Source (XML)"\n', arraybuffer]);
+ tilelayer._map._socket.sendMessage(blob);
+ console.log('Sent paste blob message');
+ } else {
+ console.log('Error code ' + oReq.status + ' fetching from URL "' + meta + '": ' + e + ' falling back to local.');
+ tilelayer._map._socket.sendMessage(content);
+ }
+ }
+ oReq.onerror = function(e) {
+ console.log('Error fetching from URL "' + meta + '": ' + e + ' falling back to local.');
+ tilelayer._map._socket.sendMessage(content);
+ };
+ oReq.open('GET', meta);
+ oReq.responseType = 'arraybuffer';
+ oReq.send();
+ // user abort - if they do stops paste.
+ } else {
+ console.log('Received a paste but nothing on the clipboard');
+ }
+ },
+
+ _readContentSync: function(dataTransfer) {
+ // Try various content mime types
var mimeTypes;
if (this._docType === 'spreadsheet') {
// FIXME apparently we cannot paste the text/html or text/rtf as
@@ -2547,15 +2598,15 @@ L.TileLayer = L.GridLayer.extend({
];
}
+ var types = dataTransfer.types;
for (var i = 0; i < mimeTypes.length; ++i) {
- for (t = 0; t < types.length; ++t) {
+ for (var t = 0; t < types.length; ++t) {
if (mimeTypes[i][0] === types[t]) {
- var blob = new Blob(['paste mimetype=' + mimeTypes[i][1] + '\n', dataTransfer.getData(types[t])]);
- this._map._socket.sendMessage(blob);
- return;
+ return new Blob(['paste mimetype=' + mimeTypes[i][1] + '\n', dataTransfer.getData(types[t])]);
}
}
}
+ return null;
},
_onFileLoadFunc: function(file) {
diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp
index f906e690d..899eae49f 100644
--- a/wsd/ClientSession.cpp
+++ b/wsd/ClientSession.cpp
@@ -14,6 +14,7 @@
#include <fstream>
#include <sstream>
#include <memory>
+#include <unordered_map>
#include <Poco/Net/HTTPResponse.h>
#include <Poco/StreamCopier.h>
@@ -35,10 +36,14 @@ using namespace LOOLProtocol;
using Poco::Path;
using Poco::StringTokenizer;
+static std::mutex SessionMapMutex;
+static std::unordered_map<std::string, std::weak_ptr<ClientSession>> SessionMap;
+
ClientSession::ClientSession(const std::string& id,
const std::shared_ptr<DocumentBroker>& docBroker,
const Poco::URI& uriPublic,
- const bool readOnly) :
+ const bool readOnly,
+ const std::string& hostNoTrust) :
Session("ToClient-" + id, id, readOnly),
_docBroker(docBroker),
_uriPublic(uriPublic),
@@ -53,16 +58,61 @@ ClientSession::ClientSession(const std::string& id,
_tileWidthTwips(0),
_tileHeightTwips(0),
_kitViewId(-1),
+ _hostNoTrust(hostNoTrust),
_isTextDocument(false)
{
const size_t curConnections = ++LOOLWSD::NumConnections;
LOG_INF("ClientSession ctor [" << getName() << "], current number of connections: " << curConnections);
}
+// Can't take a reference in the constructor.
+void ClientSession::construct()
+{
+ std::unique_lock<std::mutex> lock(SessionMapMutex);
+ SessionMap[getId()] = shared_from_this();
+}
+
ClientSession::~ClientSession()
{
const size_t curConnections = --LOOLWSD::NumConnections;
LOG_INF("~ClientSession dtor [" << getName() << "], current number of connections: " << curConnections);
+
+ std::unique_lock<std::mutex> lock(SessionMapMutex);
+ SessionMap.erase(getId());
+}
+
+std::string ClientSession::getURIAndUpdateClipboardHash()
+{
+ std::string hash = Util::rng::getHexString(16);
+ {
+ std::unique_lock<std::mutex> lock(SessionMapMutex);
+ _clipboardKey = hash;
+ }
+
+ std::string encodedFrom;
+ Poco::URI wopiSrc = getDocumentBroker()->getPublicUri();
+ wopiSrc.setQueryParameters(Poco::URI::QueryParameters());
+
+ std::string encodeChars = ",/?:@&=+$#"; // match JS encodeURIComponent
+ Poco::URI::encode(wopiSrc.toString(), encodeChars, encodedFrom);
+
+ std::string proto = (LOOLWSD::isSSLEnabled() || LOOLWSD::isSSLTermination()) ? "https://" : "http://";
+ std::string meta = proto + _hostNoTrust + "/clipboard/" + LOOLWSD::HostIdentifier +
+ "/" + std::to_string(getKitViewId()) + "?WOPISrc=" + encodedFrom + "&tag=" + hash;
+ return meta;
+}
+
+// called infrequently
+std::shared_ptr<ClientSession> ClientSession::getByClipboardHash(std::string &key)
+{
+ std::unique_lock<std::mutex> lock(SessionMapMutex);
+ for (auto &it : SessionMap)
+ {
+ auto session = it.second.lock();
+ if (session && session->_clipboardKey == key)
+ return session;
+ }
+ return std::shared_ptr<ClientSession>();
}
void ClientSession::handleIncomingMessage(SocketDisposition &disposition)
@@ -986,14 +1036,7 @@ bool ClientSession::handleKitToClientMessage(const char* buffer, const int lengt
// cf. TileLayer.js /_dataTransferToDocument/
if (pos != std::string::npos) // assume text/html
{
- // FIXME: expose other content types ? provide an RTF back-channel ?
- std::string encodedFrom;
- Poco::URI wopiSrc = docBroker->getPublicUri();
- wopiSrc.setQueryParameters(Poco::URI::QueryParameters());
- // matching encodeURIComponent
- Poco::URI::encode(wopiSrc.toString(), ",/?:@&=+$#", encodedFrom);
- std::string meta = "https://transient/" + LOOLWSD::HostIdentifier + "/" +
- std::to_string(getKitViewId()) + "?WOPISrc=" + encodedFrom;
+ std::string meta = getURIAndUpdateClipboardHash();
std::string origin = "<meta name=\"origin\" content=\"" + meta + "\"/>\n";
data.insert(data.begin() + pos, origin.begin(), origin.end());
return true;
diff --git a/wsd/ClientSession.hpp b/wsd/ClientSession.hpp
index 9562c8f39..61b3c037c 100644
--- a/wsd/ClientSession.hpp
+++ b/wsd/ClientSession.hpp
@@ -33,10 +33,14 @@ public:
ClientSession(const std::string& id,
const std::shared_ptr<DocumentBroker>& docBroker,
const Poco::URI& uriPublic,
- const bool isReadOnly = false);
-
+ const bool isReadOnly,
+ const std::string& hostNoTrust);
+ void construct();
virtual ~ClientSession();
+ /// Lookup any session by id.
+ static std::shared_ptr<ClientSession> getById(const std::string &id);
+
void handleIncomingMessage(SocketDisposition &) override;
void setReadOnly() override;
@@ -132,8 +136,15 @@ public:
void resetWireIdMap();
bool isTextDocument() const { return _isTextDocument; }
+
+ /// Find clipboard for session
+ static std::shared_ptr<ClientSession> getByClipboardHash(std::string &key);
+
private:
+ /// Create URI for transient clipboard content.
+ std::string getURIAndUpdateClipboardHash();
+
/// SocketHandler: disconnection event.
void onDisconnect() override;
/// Does SocketHandler: have data or timeouts to setup.
@@ -217,9 +228,15 @@ private:
/// The integer id of the view in the Kit process
int _kitViewId;
+ /// Un-trusted hostname of our service from the client
+ const std::string _hostNoTrust;
+
/// Client is using a text document?
bool _isTextDocument;
+ /// Transient clipboard identifier - protected by SessionMapMutex
+ std::string _clipboardKey;
+
/// TileID's of the sent tiles. Push by sending and pop by tileprocessed message from the client.
std::list<std::pair<std::string, std::chrono::steady_clock::time_point>> _tilesOnFly;
diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp
index 44652cb34..f9dafd373 100644
--- a/wsd/LOOLWSD.cpp
+++ b/wsd/LOOLWSD.cpp
@@ -687,7 +687,7 @@ inline std::string getAdminURI(const Poco::Util::LayeredConfiguration &config)
#endif // MOBILEAPP
-std::atomic<unsigned> LOOLWSD::NextSessionId;
+std::atomic<uint64_t> LOOLWSD::NextSessionId;
#ifndef KIT_IN_PROCESS
std::atomic<int> LOOLWSD::ForKitWritePipe(-1);
std::atomic<int> LOOLWSD::ForKitProcId(-1);
@@ -1762,7 +1762,8 @@ static std::shared_ptr<ClientSession> createNewClientSession(const WebSocketHand
const std::string& id,
const Poco::URI& uriPublic,
const std::shared_ptr<DocumentBroker>& docBroker,
- const bool isReadOnly)
+ const bool isReadOnly,
+ const std::string& hostNoTrust)
{
LOG_CHECK_RET(docBroker && "Null docBroker instance", nullptr);
try
@@ -1778,7 +1779,10 @@ static std::shared_ptr<ClientSession> createNewClientSession(const WebSocketHand
// In case of WOPI, if this session is not set as readonly, it might be set so
// later after making a call to WOPI host which tells us the permission on files
// (UserCanWrite param).
- return std::make_shared<ClientSession>(id, docBroker, uriPublic, isReadOnly);
+ auto session = std::make_shared<ClientSession>(id, docBroker, uriPublic, isReadOnly, hostNoTrust);
+ session->construct();
+
+ return session;
}
catch (const std::exception& exc)
{
@@ -2426,7 +2430,8 @@ private:
// Load the document.
// TODO: Move to DocumentBroker.
const bool isReadOnly = true;
- std::shared_ptr<ClientSession> clientSession = createNewClientSession(nullptr, _id, uriPublic, docBroker, isReadOnly);
+ std::shared_ptr<ClientSession> clientSession = createNewClientSession(
+ nullptr, _id, uriPublic, docBroker, isReadOnly, "nocliphost");
if (clientSession)
{
disposition.setMove([docBroker, clientSession, format]
@@ -2475,7 +2480,10 @@ private:
sent = true;
}
else
+ {
LOG_WRN("Failed to create Client Session with id [" << _id << "] on docKey [" << docKey << "].");
+ cleanupDocBrokers();
+ }
}
if (!sent)
@@ -2673,7 +2681,11 @@ private:
std::shared_ptr<DocumentBroker> docBroker = findOrCreateDocBroker(ws, url, docKey, _id, uriPublic);
if (docBroker)
{
- std::shared_ptr<ClientSession> clientSession = createNewClientSession(&ws, _id, uriPublic, docBroker, isReadOnly);
+ // We can send this back to whomever sent it to us though.
+ const std::string hostNoTrust = (LOOLWSD::ServerName.empty() ? request.getHost() : LOOLWSD::ServerName);
+
+ std::shared_ptr<ClientSession> clientSession = createNewClientSession(&ws, _id, uriPublic,
+ docBroker, isReadOnly, hostNoTrust);
if (clientSession)
{
// Transfer the client socket to the DocumentBroker when we get back to the poll:
@@ -2730,7 +2742,6 @@ private:
else
{
LOG_WRN("Failed to create Client Session with id [" << _id << "] on docKey [" << docKey << "].");
- cleanupDocBrokers();
}
}
else
diff --git a/wsd/LOOLWSD.hpp b/wsd/LOOLWSD.hpp
index 7cb03920f..97cec967f 100644
--- a/wsd/LOOLWSD.hpp
+++ b/wsd/LOOLWSD.hpp
@@ -44,7 +44,7 @@ public:
// An Application is a singleton anyway,
// so just keep these as statics.
- static std::atomic<unsigned> NextSessionId;
+ static std::atomic<uint64_t> NextSessionId;
static unsigned int NumPreSpawnedChildren;
static bool NoCapsForKit;
static bool NoSeccomp;
commit df4e17dc26a26cba05155f5102f66b5237dec074
Author: Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Sun May 26 00:16:18 2019 +0100
Commit: Michael Meeks <michael.meeks at collabora.com>
CommitDate: Tue May 28 02:26:47 2019 +0100
Ensure we have a unique ID for each document and view.
Change-Id: I9f38dd137d1617aa39b1ca505b07ab7740a986b9
diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js
index a32fa9044..6916ab775 100644
--- a/loleaflet/src/layer/tile/TileLayer.js
+++ b/loleaflet/src/layer/tile/TileLayer.js
@@ -2488,14 +2488,16 @@ L.TileLayer = L.GridLayer.extend({
if (preferInternal === true) {
var pasteHtml = dataTransfer.getData('text/html');
var meta = this._getMetaOrigin(pasteHtml);
- var id = this._map._socket.WSDServer.Id;
+ var id = 'https://transient/' + this._map._socket.WSDServer.Id + '/' + this._viewId +
+ '?WOPISrc=' + encodeURIComponent(this._map.options.doc);
+ // cf. ClientSession.cpp /textselectioncontent:/
if (meta == id) {
// Home from home: short-circuit internally.
this._map._socket.sendMessage('uno .uno:Paste');
return;
} else
- console.log('Unusual origin mismatch on paste between: "' +
- meta + '" and "' + id);
+ console.log('Unusual origin mismatch on paste between:\n\t"' +
+ meta + '" and\n\t"' + id + '"');
}
var types = dataTransfer.types;
diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp
index c6d6def28..f906e690d 100644
--- a/wsd/ClientSession.cpp
+++ b/wsd/ClientSession.cpp
@@ -52,6 +52,7 @@ ClientSession::ClientSession(const std::string& id,
_tileHeightPixel(0),
_tileWidthTwips(0),
_tileHeightTwips(0),
+ _kitViewId(-1),
_isTextDocument(false)
{
const size_t curConnections = ++LOOLWSD::NumConnections;
@@ -979,12 +980,21 @@ bool ClientSession::handleKitToClientMessage(const char* buffer, const int lengt
}
} else if (tokens[0] == "textselectioncontent:") {
// Insert our meta origin if we can
- payload->rewriteDataBody([](std::vector<char>& data) {
+ payload->rewriteDataBody([=](std::vector<char>& data) {
size_t pos = Util::findInVector(data, "<meta name=\"generator\" content=\"");
+
+ // cf. TileLayer.js /_dataTransferToDocument/
if (pos != std::string::npos) // assume text/html
{
- // FIXME: expose other content types ? provide an RTF back-channel ? WOPISRC ?
- std::string origin = "<meta name=\"origin\" content=\"" + LOOLWSD::HostIdentifier + "\"/>\n";
+ // FIXME: expose other content types ? provide an RTF back-channel ?
+ std::string encodedFrom;
+ Poco::URI wopiSrc = docBroker->getPublicUri();
+ wopiSrc.setQueryParameters(Poco::URI::QueryParameters());
+ // matching encodeURIComponent
+ Poco::URI::encode(wopiSrc.toString(), ",/?:@&=+$#", encodedFrom);
+ std::string meta = "https://transient/" + LOOLWSD::HostIdentifier + "/" +
+ std::to_string(getKitViewId()) + "?WOPISrc=" + encodedFrom;
+ std::string origin = "<meta name=\"origin\" content=\"" + meta + "\"/>\n";
data.insert(data.begin() + pos, origin.begin(), origin.end());
return true;
}
@@ -1024,6 +1034,11 @@ bool ClientSession::handleKitToClientMessage(const char* buffer, const int lengt
{
_isTextDocument = docType.find("text") != std::string::npos;
}
+
+ // Store our Kit ViewId
+ int viewId = -1;
+ if(getTokenInteger(token, "viewid", viewId))
+ _kitViewId = viewId;
}
// Forward the status response to the client.
diff --git a/wsd/ClientSession.hpp b/wsd/ClientSession.hpp
index b701e5568..9562c8f39 100644
--- a/wsd/ClientSession.hpp
+++ b/wsd/ClientSession.hpp
@@ -55,6 +55,9 @@ public:
/// Handle kit-to-client message.
bool handleKitToClientMessage(const char* data, const int size);
+ /// Integer id of the view in the kit process, or -1 if unknown
+ int getKitViewId() const { return _kitViewId; }
+
// sendTextFrame that takes std::string and string literal.
using Session::sendTextFrame;
@@ -211,6 +214,9 @@ private:
int _tileWidthTwips;
int _tileHeightTwips;
+ /// The integer id of the view in the Kit process
+ int _kitViewId;
+
/// Client is using a text document?
bool _isTextDocument;
commit 4645bbbd36ade52e5b1b3557453c515f665c6824
Author: Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Sat May 25 16:13:54 2019 +0100
Commit: Michael Meeks <michael.meeks at collabora.com>
CommitDate: Tue May 28 02:26:47 2019 +0100
Initial switch of outbound copy format to HTML.
Use a content-editable instead of an input
Switch format specifiers left and right.
Embed a <meta name="origin" content="<Id>"/> to reliably detect
internal copy/paste not based on content.
Lots of testing.
Change-Id: I36723298e392331515b055b5ebe8132fb2e4fa3c
diff --git a/common/Message.hpp b/common/Message.hpp
index a62cfe375..3fdbc5d36 100644
--- a/common/Message.hpp
+++ b/common/Message.hpp
@@ -13,6 +13,7 @@
#include <atomic>
#include <string>
#include <vector>
+#include <functional>
#include "Protocol.hpp"
#include "Log.hpp"
@@ -117,6 +118,18 @@ public:
/// Returns true if and only if the payload is considered Binary.
bool isBinary() const { return _type == Type::Binary; }
+ /// Allows some in-line re-writing of the message
+ void rewriteDataBody(std::function<bool (std::vector<char> &)> func)
+ {
+ if (func(_data))
+ {
+ // Check - just the body.
+ assert(_firstLine == LOOLProtocol::getFirstLine(_data.data(), _data.size()));
+ assert(_abbr == _id + ' ' + LOOLProtocol::getAbbreviatedMessage(_data.data(), _data.size()));
+ assert(_type == detectType());
+ }
+ }
+
private:
/// Constructs a unique ID.
diff --git a/common/Util.cpp b/common/Util.cpp
index 6f2322c8f..dcdbd115b 100644
--- a/common/Util.cpp
+++ b/common/Util.cpp
@@ -739,6 +739,20 @@ namespace Util
return base + Util::anonymize(filename, nAnonymizationSalt) + ext + params;
}
+
+ size_t findInVector(const std::vector<char>& tokens, const char *cstring)
+ {
+ assert(cstring);
+ for (size_t i = 0; i < tokens.size(); ++i)
+ {
+ size_t j;
+ for (j = 0; i + j < tokens.size() && cstring[j] != '\0' && tokens[i + j] == cstring[j]; ++j)
+ ;
+ if (cstring[j] == '\0')
+ return i;
+ }
+ return std::string::npos;
+ }
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/common/Util.hpp b/common/Util.hpp
index 9021b7ed1..488f9d525 100644
--- a/common/Util.hpp
+++ b/common/Util.hpp
@@ -256,6 +256,8 @@ namespace Util
return oss.str();
}
+ size_t findInVector(const std::vector<char>& tokens, const char *cstring);
+
/// Trim spaces from the left. Just spaces.
inline std::string& ltrim(std::string& s)
{
diff --git a/loleaflet/src/core/Socket.js b/loleaflet/src/core/Socket.js
index b48338420..17ea4bb09 100644
--- a/loleaflet/src/core/Socket.js
+++ b/loleaflet/src/core/Socket.js
@@ -36,6 +36,7 @@ L.Socket = L.Class.extend({
ProtocolVersionNumber: '0.1',
ReconnectCount: 0,
WasShownLimitDialog: false,
+ WSDServer: {},
getParameterValue: function (s) {
var i = s.indexOf('=');
@@ -275,25 +276,25 @@ L.Socket = L.Class.extend({
var command = this.parseServerCmd(textMsg);
if (textMsg.startsWith('loolserver ')) {
// This must be the first message, unless we reconnect.
- var loolwsdVersionObj = JSON.parse(textMsg.substring(textMsg.indexOf('{')));
- var h = loolwsdVersionObj.Hash;
+ this.WSDServer = JSON.parse(textMsg.substring(textMsg.indexOf('{')));
+ var h = this.WSDServer.Hash;
if (parseInt(h,16).toString(16) === h.toLowerCase().replace(/^0+/, '')) {
if (!window.ThisIsTheiOSApp) {
h = '<a target="_blank" href="https://hub.libreoffice.org/git-online/' + h + '">' + h + '</a>';
}
- $('#loolwsd-version').html(loolwsdVersionObj.Version + ' (git hash: ' + h + ')');
+ $('#loolwsd-version').html(this.WSDServer.Version + ' (git hash: ' + h + ')');
}
else {
- $('#loolwsd-version').text(loolwsdVersionObj.Version);
+ $('#loolwsd-version').text(this.WSDServer.Version);
}
var idUri = this._map.options.server + this._map.options.serviceRoot + '/hosting/discovery';
idUri = idUri.replace(/^ws:/, 'http:');
idUri = idUri.replace(/^wss:/, 'https:');
- $('#loolwsd-id').html('<a href="' + idUri + '">' + loolwsdVersionObj.Id + '</a>');
+ $('#loolwsd-id').html('<a href="' + idUri + '">' + this.WSDServer.Id + '</a>');
// TODO: For now we expect perfect match in protocol versions
- if (loolwsdVersionObj.Protocol !== this.ProtocolVersionNumber) {
+ if (this.WSDServer.Protocol !== this.ProtocolVersionNumber) {
this._map.fire('error', {msg: _('Unsupported server version.')});
}
}
diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js
index 41aae051d..f22ed2feb 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -41,15 +41,25 @@ L.ClipboardContainer = L.Layer.extend({
},
select: function() {
- this._textArea.select();
+ window.getSelection().selectAllChildren(this._textArea);
+ },
+
+ resetToSelection: function() {
+ var sel = window.getSelection();
+ if (sel.anchorNode == this._textArea) {
+ // already selected, don't reset to plain-text from toString()
+ } else {
+ this.setValue(sel.toString());
+ }
},
getValue: function() {
- return this._textArea.value;
+ return this._textArea.innerHTML;
},
setValue: function(val) {
- this._textArea.value = val;
+ this._textArea.innerHTML = val;
+ this.select();
},
setLatLng: function (latlng) {
@@ -67,7 +77,8 @@ L.ClipboardContainer = L.Layer.extend({
_initLayout: function () {
this._container = L.DomUtil.create('div', 'clipboard-container');
this._container.id = 'doc-clipboard-container';
- this._textArea = L.DomUtil.create('input', 'clipboard', this._container);
+ this._textArea = L.DomUtil.create('div', 'clipboard', this._container);
+ this._textArea.setAttribute('contenteditable', 'true');
this._textArea.setAttribute('type', 'text');
this._textArea.setAttribute('autocorrect', 'off');
this._textArea.setAttribute('autocapitalize', 'off');
diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js
index a1488669a..a32fa9044 100644
--- a/loleaflet/src/layer/tile/TileLayer.js
+++ b/loleaflet/src/layer/tile/TileLayer.js
@@ -26,12 +26,18 @@ function hex2string(inData)
}
L.Compatibility = {
+ stripHTML: function(html) { // grim.
+ var tmp = document.createElement('div');
+ tmp.innerHTML = html;
+ return tmp.textContent || tmp.innerText || '';
+ },
+
clipboardSet: function (event, text) {
if (event.clipboardData) { // Standard
- event.clipboardData.setData('text/plain', text);
+ event.clipboardData.setData('text/html', text);
}
- else if (window.clipboardData) { // IE 11
- window.clipboardData.setData('Text', text);
+ else if (window.clipboardData) { // IE 11 - poor clipboard API
+ window.clipboardData.setData('Text', this.stripHTML(text));
}
}
};
@@ -1227,7 +1233,7 @@ L.TileLayer = L.GridLayer.extend({
clearTimeout(this._selectionContentRequest);
}
this._selectionContentRequest = setTimeout(L.bind(function () {
- this._map._socket.sendMessage('gettextselection mimetype=text/plain;charset=utf-8');}, this), 100);
+ this._map._socket.sendMessage('gettextselection mimetype=text/html');}, this), 100);
}
this._onUpdateTextSelection();
},
@@ -1267,6 +1273,7 @@ L.TileLayer = L.GridLayer.extend({
_onTextSelectionContentMsg: function (textMsg) {
this._selectionTextContent = textMsg.substr(22);
+ this._map._clipboardContainer.setValue(this._selectionTextContent);
},
_updateScrollOnCellSelection: function (oldSelection, newSelection) {
@@ -2463,18 +2470,32 @@ L.TileLayer = L.GridLayer.extend({
this._dataTransferToDocument(e.dataTransfer, /* preferInternal = */ false);
},
+ _getMetaOrigin: function (html) {
+ var match = '<meta name="origin" content="';
+ var start = html.indexOf(match);
+ if (start < 0) {
+ return '';
+ }
+ var end = html.indexOf('"', start + match.length);
+ if (end < 0) {
+ return '';
+ }
+ return html.substring(start + match.length, end);
+ },
+
_dataTransferToDocument: function (dataTransfer, preferInternal) {
// for the paste, we might prefer the internal LOK's copy/paste
if (preferInternal === true) {
- var pasteString = dataTransfer.getData('text/plain');
- if (!pasteString) {
- pasteString = dataTransfer.getData('Text'); // IE 11
- }
-
- if (pasteString && pasteString === this._selectionTextHash) {
+ var pasteHtml = dataTransfer.getData('text/html');
+ var meta = this._getMetaOrigin(pasteHtml);
+ var id = this._map._socket.WSDServer.Id;
+ if (meta == id) {
+ // Home from home: short-circuit internally.
this._map._socket.sendMessage('uno .uno:Paste');
return;
- }
+ } else
+ console.log('Unusual origin mismatch on paste between: "' +
+ meta + '" and "' + id);
}
var types = dataTransfer.types;
diff --git a/loleaflet/src/map/handler/Map.Keyboard.js b/loleaflet/src/map/handler/Map.Keyboard.js
index 4e3b51354..95a5269b9 100644
--- a/loleaflet/src/map/handler/Map.Keyboard.js
+++ b/loleaflet/src/map/handler/Map.Keyboard.js
@@ -528,7 +528,7 @@ L.Map.Keyboard = L.Handler.extend({
case 91: // Left Cmd (Safari)
case 93: // Right Cmd (Safari)
// we prepare for a copy or cut event
- this._map._clipboardContainer.setValue(window.getSelection().toString());
+ this._map._clipboardContainer.resetToSelection();
this._map.focus();
this._map._clipboardContainer.select();
return true;
diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp
index 3de6a8651..c6d6def28 100644
--- a/wsd/ClientSession.cpp
+++ b/wsd/ClientSession.cpp
@@ -977,6 +977,24 @@ bool ClientSession::handleKitToClientMessage(const char* buffer, const int lengt
}
}
}
+ } else if (tokens[0] == "textselectioncontent:") {
+ // Insert our meta origin if we can
+ payload->rewriteDataBody([](std::vector<char>& data) {
+ size_t pos = Util::findInVector(data, "<meta name=\"generator\" content=\"");
+ if (pos != std::string::npos) // assume text/html
+ {
+ // FIXME: expose other content types ? provide an RTF back-channel ? WOPISRC ?
+ std::string origin = "<meta name=\"origin\" content=\"" + LOOLWSD::HostIdentifier + "\"/>\n";
+ data.insert(data.begin() + pos, origin.begin(), origin.end());
+ return true;
+ }
+ else
+ {
+ LOG_DBG("Missing generator in textselectioncontent");
+ return false;
+ }
+ });
+ return forwardToClient(payload);
}
if (!isDocPasswordProtected())
commit ce18e7b50a3498dc26b032605529d0f5922ca37e
Author: Jan Holesovsky <kendy at collabora.com>
AuthorDate: Mon May 27 16:00:23 2019 +0200
Commit: Szymon KÅos <szymon.klos at collabora.com>
CommitDate: Mon May 27 18:27:46 2019 +0200
Enable the Share and Print buttons even in the View mode.
The user should be able to access these even in the initial View mode on
Android.
Change-Id: Id0631b7560c1ed0fda5f228f0c621cfa989b4cf7
Reviewed-on: https://gerrit.libreoffice.org/73039
Reviewed-by: Szymon KÅos <szymon.klos at collabora.com>
Tested-by: Szymon KÅos <szymon.klos at collabora.com>
diff --git a/loleaflet/src/control/Control.Menubar.js b/loleaflet/src/control/Control.Menubar.js
index 35e0e1d97..02c44ad97 100644
--- a/loleaflet/src/control/Control.Menubar.js
+++ b/loleaflet/src/control/Control.Menubar.js
@@ -437,6 +437,7 @@ L.Control.Menubar = L.Control.extend({
allowedReadonlyMenus: ['file', 'downloadas', 'view', 'help'],
allowedViewModeActions: [
+ 'shareas', 'print', // file menu
'downloadas-pdf', 'downloadas-odt', 'downloadas-doc', 'downloadas-docx', 'downloadas-rtf', // file menu
'downloadas-odp', 'downloadas-ppt', 'downloadas-pptx', 'print', // file menu
'downloadas-ods', 'downloadas-xls', 'downloadas-xlsx', 'closedocument', // file menu
commit 1288286d453c0df2b82f0a8b7cc16f5b2d66cb13
Author: merttumer <mert.tumer at collabora.com>
AuthorDate: Mon May 27 17:11:34 2019 +0300
Commit: merttumer <mert.tumer at collabora.com>
CommitDate: Mon May 27 17:38:15 2019 +0300
Send postMessage after renaming the document
Change-Id: Iee0854260fc0fab4de3e2a4fae54716009535b37
Signed-off-by: merttumer <mert.tumer at collabora.com>
diff --git a/loleaflet/src/core/Socket.js b/loleaflet/src/core/Socket.js
index 56402e4d5..b48338420 100644
--- a/loleaflet/src/core/Socket.js
+++ b/loleaflet/src/core/Socket.js
@@ -742,13 +742,16 @@ L.Socket = L.Class.extend({
this._map.options.wopiSrc = encodeURIComponent(docUrl);
this._map.loadDocument();
this._map.sendInitUNOCommands();
-
- this._map.fire('postMessage', {
- msgId: 'File_Rename',
- args: {
- NewName: command.filename
- }
- });
+
+ if (textMsg.startsWith('renamefile:')) {
+ this._map.fire('postMessage', {
+ msgId: 'File_Rename',
+ args: {
+ NewName: command.filename
+ }
+ });
+ }
+
}
// var name = command.name; - ignored, we get the new name via the wopi's BaseFileName
}
commit f426e36f69759d8bded61a62f1fcc3e0f4c0bb8c
Author: merttumer <mert.tumer at collabora.com>
AuthorDate: Mon May 27 17:11:34 2019 +0300
Commit: merttumer <mert.tumer at collabora.com>
CommitDate: Mon May 27 17:11:34 2019 +0300
Send postMessage after renaming the document
Change-Id: Iee0854260fc0fab4de3e2a4fae54716009535b37
Signed-off-by: merttumer <mert.tumer at collabora.com>
diff --git a/loleaflet/src/core/Socket.js b/loleaflet/src/core/Socket.js
index 06fa5eb47..56402e4d5 100644
--- a/loleaflet/src/core/Socket.js
+++ b/loleaflet/src/core/Socket.js
@@ -742,6 +742,13 @@ L.Socket = L.Class.extend({
this._map.options.wopiSrc = encodeURIComponent(docUrl);
this._map.loadDocument();
this._map.sendInitUNOCommands();
+
+ this._map.fire('postMessage', {
+ msgId: 'File_Rename',
+ args: {
+ NewName: command.filename
+ }
+ });
}
// var name = command.name; - ignored, we get the new name via the wopi's BaseFileName
}
@@ -988,6 +995,9 @@ L.Socket = L.Class.extend({
else if (tokens[i].substring(0, 5) === 'name=') {
command.name = tokens[i].substring(5);
}
+ else if (tokens[i].substring(0, 9) === 'filename=') {
+ command.filename = tokens[i].substring(9);
+ }
else if (tokens[i].substring(0, 5) === 'port=') {
command.port = tokens[i].substring(5);
}
More information about the Libreoffice-commits
mailing list