[Libreoffice-commits] online.git: Branch 'distro/collabora/collabora-online-4' - 4 commits - common/Message.hpp common/Util.cpp common/Util.hpp loleaflet/src wsd/ClientSession.cpp wsd/ClientSession.hpp wsd/LOOLWSD.cpp wsd/LOOLWSD.hpp

Libreoffice Gerrit user logerrit at kemper.freedesktop.org
Tue May 28 01:27:11 UTC 2019


 common/Message.hpp                               |   13 ++
 common/Util.cpp                                  |   14 ++
 common/Util.hpp                                  |    2 
 loleaflet/src/core/Socket.js                     |   13 +-
 loleaflet/src/layer/marker/ClipboardContainer.js |   19 ++-
 loleaflet/src/layer/tile/TileLayer.js            |  141 +++++++++++++++++------
 loleaflet/src/map/handler/Map.Keyboard.js        |    2 
 wsd/ClientSession.cpp                            |   95 +++++++++++++++
 wsd/ClientSession.hpp                            |   27 ++++
 wsd/LOOLWSD.cpp                                  |   99 +++++++++++++++-
 wsd/LOOLWSD.hpp                                  |    2 
 11 files changed, 372 insertions(+), 55 deletions(-)

New commits:
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())


More information about the Libreoffice-commits mailing list