[Libreoffice-commits] online.git: common/Message.hpp common/Util.cpp common/Util.hpp kit/ChildSession.cpp kit/ChildSession.hpp loleaflet/src wsd/ClientSession.cpp wsd/ClientSession.hpp wsd/LOOLWSD.cpp wsd/LOOLWSD.hpp

Michael Meeks (via logerrit) logerrit at kemper.freedesktop.org
Mon Aug 5 19:50:38 UTC 2019


 common/Message.hpp                               |   13 +
 common/Util.cpp                                  |   14 ++
 common/Util.hpp                                  |    2 
 kit/ChildSession.cpp                             |   12 +
 kit/ChildSession.hpp                             |   18 ++
 loleaflet/src/core/Socket.js                     |   13 +
 loleaflet/src/layer/marker/ClipboardContainer.js |   19 ++
 loleaflet/src/layer/tile/TileLayer.js            |  146 ++++++++++++++++-----
 loleaflet/src/map/handler/Map.Keyboard.js        |    2 
 wsd/ClientSession.cpp                            |  154 ++++++++++++++++++++++-
 wsd/ClientSession.hpp                            |   32 ++++
 wsd/LOOLWSD.cpp                                  |   20 ++
 wsd/LOOLWSD.hpp                                  |    2 
 13 files changed, 388 insertions(+), 59 deletions(-)

New commits:
commit 52e477e57eb7c6df8bbc085b603fdb25b314d63d
Author:     Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Wed May 29 16:26:16 2019 +0100
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Mon Aug 5 15:47:47 2019 -0400

    Switch to text/html for paste where we can.
    
    Build special URLs to detect the same host being in-use, and much more.
    
    Change-Id: I0ca639ea416cb78bf5e5274eac4400542b6b2cda

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 9185e04c4..4ab141f64 100644
--- a/common/Util.cpp
+++ b/common/Util.cpp
@@ -739,6 +739,20 @@ namespace Util
 
         return time_now;
     }
+
+    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 e6e426ed0..359d6c0e2 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/kit/ChildSession.cpp b/kit/ChildSession.cpp
index 01853e94b..ab9f83ef5 100644
--- a/kit/ChildSession.cpp
+++ b/kit/ChildSession.cpp
@@ -922,12 +922,22 @@ bool ChildSession::paste(const char* buffer, int length, const std::vector<std::
     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)
     {
         getLOKitDocument()->setView(_viewId);
 
-        getLOKitDocument()->paste(mimeType.c_str(), data, size);
+        LOG_TRC("Paste data of size " << size << " bytes and hash " << SpookyHash::Hash64(data, size, 0));
+        success = getLOKitDocument()->paste(mimeType.c_str(), data, size);
+        if (!success)
+            LOG_WRN("Paste failed " << getLOKitLastError());
     }
+    if (success)
+        result += "success";
+    else
+        result += "fallback";
+    sendTextFrame(result);
 
     return true;
 }
diff --git a/kit/ChildSession.hpp b/kit/ChildSession.hpp
index bb6a3d720..737b7d8e6 100644
--- a/kit/ChildSession.hpp
+++ b/kit/ChildSession.hpp
@@ -53,12 +53,12 @@ public:
     /// if it is the last and only.
     virtual void onUnload(const ChildSession& session) = 0;
 
+    /// Access to the Kit instance.
+    virtual std::shared_ptr<lok::Office> getLOKit() = 0;
+
     /// Access to the document instance.
     virtual std::shared_ptr<lok::Document> getLOKitDocument() = 0;
 
-    /// Access to the office instance.
-    virtual std::shared_ptr<lok::Office> getLOKit() = 0;
-
     /// Send updated view info to all active sessions.
     virtual void notifyViewInfo() = 0;
     virtual void updateEditorSpeeds(int id, int speed) = 0;
@@ -278,6 +278,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/core/Socket.js b/loleaflet/src/core/Socket.js
index 366918123..1856511fa 100644
--- a/loleaflet/src/core/Socket.js
+++ b/loleaflet/src/core/Socket.js
@@ -9,6 +9,7 @@ L.Socket = L.Class.extend({
 	ProtocolVersionNumber: '0.1',
 	ReconnectCount: 0,
 	WasShownLimitDialog: false,
+	WSDServer: {},
 
 	getParameterValue: function (s) {
 		var i = s.indexOf('=');
@@ -238,25 +239,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 ac1f01185..1440a5ff8 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -45,15 +45,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) {
@@ -71,7 +81,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 d7f74a773..9313c59be 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));
 		}
 	}
 };
@@ -521,6 +527,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);
 		}
@@ -1268,7 +1277,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();
 	},
@@ -1308,6 +1317,7 @@ L.TileLayer = L.GridLayer.extend({
 
 	_onTextSelectionContentMsg: function (textMsg) {
 		this._selectionTextContent = textMsg.substr(22);
+		this._map._clipboardContainer.setValue(this._selectionTextContent);
 	},
 
 	_updateScrollOnCellSelection: function (oldSelection, newSelection) {
@@ -2691,45 +2701,113 @@ L.TileLayer = L.GridLayer.extend({
 		this._dataTransferToDocument(e.dataTransfer, /* preferInternal = */ false);
 	},
 
-	_dataTransferToDocument: function (dataTransfer, preferInternal) {
-		// for the paste, we might prefer the internal LOK's copy/paste
-		if (preferInternal === true) {
-			var pasteString = dataTransfer.getData('text/plain');
-			if (!pasteString) {
-				pasteString = dataTransfer.getData('Text'); // IE 11
+	_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 '';
+		}
+		var meta = html.substring(start + match.length, end);
+
+		// quick sanity checks that it one of ours.
+		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 '';
+	},
+
+	// 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.');
 			}
+		}
 
-			if (pasteString && pasteString === this._selectionTextHash) {
-				this._map._socket.sendMessage('uno .uno:Paste');
-				return;
-			}
+		this._pasteFallback = null;
+	},
+
+	_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?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) {
+			// Home from home: short-circuit internally.
+			console.log('short-circuit, internal paste');
+			this._map._socket.sendMessage('uno .uno:Paste');
+			return;
 		}
 
-		var types = dataTransfer.types;
+		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);
 
-		// 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
+		if (content != null) {
+			console.log('Normal HTML, so smart paste not possible');
+			this._map._socket.sendMessage(content);
+			this._pasteFallback = null;
+		} else {
+			console.log('Nothing we can paste 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
-			// 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']
 			];
@@ -2752,15 +2830,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/loleaflet/src/map/handler/Map.Keyboard.js b/loleaflet/src/map/handler/Map.Keyboard.js
index d13cc37e2..8097f9c74 100644
--- a/loleaflet/src/map/handler/Map.Keyboard.js
+++ b/loleaflet/src/map/handler/Map.Keyboard.js
@@ -531,7 +531,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 6838e24ff..b8363ab98 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),
@@ -52,16 +57,93 @@ ClientSession::ClientSession(const std::string& id,
     _tileHeightPixel(0),
     _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;
+        LOG_TRC("Clipboard key on [" << getId() << "] set to " << _clipboardKey);
+   }
+
+    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?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
+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)
+        {
+            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)
@@ -949,6 +1031,60 @@ 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=\"");
+
+                // cf. TileLayer.js /_dataTransferToDocument/
+                if (pos != std::string::npos) // assume text/html
+                {
+                    std::string meta = getURIAndUpdateClipboardHash();
+                    std::string origin = "<meta name=\"origin\" content=\"" + meta + "\"/>\n";
+                    data.insert(data.begin() + pos, origin.begin(), origin.end());
+                    return true;
+                }
+                else
+                {
+                    LOG_DBG("Missing generator in textselectioncontent");
+                    return false;
+                }
+            });
+        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())
@@ -985,6 +1121,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.
@@ -1260,7 +1401,16 @@ 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
+       << "\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 8db5b3718..283ca1b83 100644
--- a/wsd/ClientSession.hpp
+++ b/wsd/ClientSession.hpp
@@ -32,10 +32,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;
@@ -54,6 +58,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;
 
@@ -133,8 +140,17 @@ public:
     void resetWireIdMap();
 
     bool isTextDocument() const { return _isTextDocument; }
+
+    /// 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.
+    std::string getURIAndUpdateClipboardHash();
+
     /// SocketHandler: disconnection event.
     void onDisconnect() override;
     /// Does SocketHandler: have data or timeouts to setup.
@@ -212,9 +228,18 @@ private:
     int _tileWidthTwips;
     int _tileHeightTwips;
 
+    /// 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;
 
@@ -223,6 +248,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 d777b38bf..e7bdbafe7 100644
--- a/wsd/LOOLWSD.cpp
+++ b/wsd/LOOLWSD.cpp
@@ -685,7 +685,8 @@ 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);
@@ -1740,7 +1741,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
@@ -1756,7 +1758,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)
     {
@@ -2406,7 +2411,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]
@@ -2661,7 +2667,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:
diff --git a/wsd/LOOLWSD.hpp b/wsd/LOOLWSD.hpp
index 6928cb6f9..83f950bad 100644
--- a/wsd/LOOLWSD.hpp
+++ b/wsd/LOOLWSD.hpp
@@ -45,7 +45,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;


More information about the Libreoffice-commits mailing list