[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