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

New commits:
commit 52e477e57eb7c6df8bbc085b603fdb25b314d63d
Author:     Michael Meeks <[email protected]>
AuthorDate: Wed May 29 16:26:16 2019 +0100
Commit:     Michael Meeks <[email protected]>
CommitDate: Mon Aug 5 15:47:47 2019 -0400

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

diff --git a/common/Message.hpp b/common/Message.hpp
index a62cfe375..3fdbc5d36 100644
--- a/common/Message.hpp
+++ b/common/Message.hpp
@@ -13,6 +13,7 @@
 #include <atomic>
 #include <string>
 #include <vector>
+#include <functional>
 
 #include "Protocol.hpp"
 #include "Log.hpp"
@@ -117,6 +118,18 @@ public:
     /// Returns true if and only if the payload is considered Binary.
     bool isBinary() const { return _type == Type::Binary; }
 
+    /// Allows some in-line re-writing of the message
+    void rewriteDataBody(std::function<bool (std::vector<char> &)> func)
+    {
+        if (func(_data))
+        {
+            // Check - just the body.
+            assert(_firstLine == LOOLProtocol::getFirstLine(_data.data(), 
_data.size()));
+            assert(_abbr == _id + ' ' + 
LOOLProtocol::getAbbreviatedMessage(_data.data(), _data.size()));
+            assert(_type == detectType());
+        }
+    }
+
 private:
 
     /// Constructs a unique ID.
diff --git a/common/Util.cpp b/common/Util.cpp
index 9185e04c4..4ab141f64 100644
--- a/common/Util.cpp
+++ b/common/Util.cpp
@@ -739,6 +739,20 @@ namespace Util
 
         return time_now;
     }
+
+    size_t findInVector(const std::vector<char>& tokens, const char *cstring)
+    {
+        assert(cstring);
+        for (size_t i = 0; i < tokens.size(); ++i)
+        {
+            size_t j;
+            for (j = 0; i + j < tokens.size() && cstring[j] != '\0' && 
tokens[i + j] == cstring[j]; ++j)
+                ;
+            if (cstring[j] == '\0')
+                return i;
+        }
+        return std::string::npos;
+    }
 }
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/common/Util.hpp b/common/Util.hpp
index e6e426ed0..359d6c0e2 100644
--- a/common/Util.hpp
+++ b/common/Util.hpp
@@ -256,6 +256,8 @@ namespace Util
         return oss.str();
     }
 
+    size_t findInVector(const std::vector<char>& tokens, const char *cstring);
+
     /// Trim spaces from the left. Just spaces.
     inline std::string& ltrim(std::string& s)
     {
diff --git a/kit/ChildSession.cpp b/kit/ChildSession.cpp
index 01853e94b..ab9f83ef5 100644
--- a/kit/ChildSession.cpp
+++ b/kit/ChildSession.cpp
@@ -922,12 +922,22 @@ bool ChildSession::paste(const char* buffer, int length, 
const std::vector<std::
     const std::string firstLine = getFirstLine(buffer, length);
     const char* data = buffer + firstLine.size() + 1;
     const int size = length - firstLine.size() - 1;
+    bool success = false;
+    std::string result = "pasteresult: ";
     if (size > 0)
     {
         getLOKitDocument()->setView(_viewId);
 
-        getLOKitDocument()->paste(mimeType.c_str(), data, size);
+        LOG_TRC("Paste data of size " << size << " bytes and hash " << 
SpookyHash::Hash64(data, size, 0));
+        success = getLOKitDocument()->paste(mimeType.c_str(), data, size);
+        if (!success)
+            LOG_WRN("Paste failed " << getLOKitLastError());
     }
+    if (success)
+        result += "success";
+    else
+        result += "fallback";
+    sendTextFrame(result);
 
     return true;
 }
diff --git a/kit/ChildSession.hpp b/kit/ChildSession.hpp
index bb6a3d720..737b7d8e6 100644
--- a/kit/ChildSession.hpp
+++ b/kit/ChildSession.hpp
@@ -53,12 +53,12 @@ public:
     /// if it is the last and only.
     virtual void onUnload(const ChildSession& session) = 0;
 
+    /// Access to the Kit instance.
+    virtual std::shared_ptr<lok::Office> getLOKit() = 0;
+
     /// Access to the document instance.
     virtual std::shared_ptr<lok::Document> getLOKitDocument() = 0;
 
-    /// Access to the office instance.
-    virtual std::shared_ptr<lok::Office> getLOKit() = 0;
-
     /// Send updated view info to all active sessions.
     virtual void notifyViewInfo() = 0;
     virtual void updateEditorSpeeds(int id, int speed) = 0;
@@ -278,6 +278,18 @@ private:
         return _docManager.getLOKitDocument();
     }
 
+    std::string getLOKitLastError()
+    {
+        char *lastErr = _docManager.getLOKit()->getError();
+        std::string ret;
+        if (lastErr)
+        {
+            ret = std::string(lastErr, strlen(lastErr));
+            free (lastErr);
+        }
+        return ret;
+    }
+
 private:
     const std::string _jailId;
     DocumentManagerInterface& _docManager;
diff --git a/loleaflet/src/core/Socket.js b/loleaflet/src/core/Socket.js
index 366918123..1856511fa 100644
--- a/loleaflet/src/core/Socket.js
+++ b/loleaflet/src/core/Socket.js
@@ -9,6 +9,7 @@ L.Socket = L.Class.extend({
        ProtocolVersionNumber: '0.1',
        ReconnectCount: 0,
        WasShownLimitDialog: false,
+       WSDServer: {},
 
        getParameterValue: function (s) {
                var i = s.indexOf('=');
@@ -238,25 +239,25 @@ L.Socket = L.Class.extend({
                var command = this.parseServerCmd(textMsg);
                if (textMsg.startsWith('loolserver ')) {
                        // This must be the first message, unless we reconnect.
-                       var loolwsdVersionObj = 
JSON.parse(textMsg.substring(textMsg.indexOf('{')));
-                       var h = loolwsdVersionObj.Hash;
+                       this.WSDServer = 
JSON.parse(textMsg.substring(textMsg.indexOf('{')));
+                       var h = this.WSDServer.Hash;
                        if (parseInt(h,16).toString(16) === 
h.toLowerCase().replace(/^0+/, '')) {
                                if (!window.ThisIsTheiOSApp) {
                                        h = '<a target="_blank" 
href="https://hub.libreoffice.org/git-online/' + h + '">' + h + '</a>';
                                }
-                               
$('#loolwsd-version').html(loolwsdVersionObj.Version + ' (git hash: ' + h + 
')');
+                               
$('#loolwsd-version').html(this.WSDServer.Version + ' (git hash: ' + h + ')');
                        }
                        else {
-                               
$('#loolwsd-version').text(loolwsdVersionObj.Version);
+                               
$('#loolwsd-version').text(this.WSDServer.Version);
                        }
 
                        var idUri = this._map.options.server + 
this._map.options.serviceRoot + '/hosting/discovery';
                        idUri = idUri.replace(/^ws:/, 'http:');
                        idUri = idUri.replace(/^wss:/, 'https:');
-                       $('#loolwsd-id').html('<a href="' + idUri + '">' + 
loolwsdVersionObj.Id + '</a>');
+                       $('#loolwsd-id').html('<a href="' + idUri + '">' + 
this.WSDServer.Id + '</a>');
 
                        // TODO: For now we expect perfect match in protocol 
versions
-                       if (loolwsdVersionObj.Protocol !== 
this.ProtocolVersionNumber) {
+                       if (this.WSDServer.Protocol !== 
this.ProtocolVersionNumber) {
                                this._map.fire('error', {msg: _('Unsupported 
server version.')});
                        }
                }
diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js 
b/loleaflet/src/layer/marker/ClipboardContainer.js
index ac1f01185..1440a5ff8 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -45,15 +45,25 @@ L.ClipboardContainer = L.Layer.extend({
        },
 
        select: function() {
-               this._textArea.select();
+               window.getSelection().selectAllChildren(this._textArea);
+       },
+
+       resetToSelection: function() {
+               var sel = window.getSelection();
+               if (sel.anchorNode == this._textArea) {
+                       // already selected, don't reset to plain-text from 
toString()
+               } else {
+                       this.setValue(sel.toString());
+               }
        },
 
        getValue: function() {
-               return this._textArea.value;
+               return this._textArea.innerHTML;
        },
 
        setValue: function(val) {
-               this._textArea.value = val;
+               this._textArea.innerHTML = val;
+               this.select();
        },
 
        setLatLng: function (latlng) {
@@ -71,7 +81,8 @@ L.ClipboardContainer = L.Layer.extend({
        _initLayout: function () {
                this._container = L.DomUtil.create('div', 
'clipboard-container');
                this._container.id = 'doc-clipboard-container';
-               this._textArea = L.DomUtil.create('input', 'clipboard', 
this._container);
+               this._textArea = L.DomUtil.create('div', 'clipboard', 
this._container);
+               this._textArea.setAttribute('contenteditable', 'true');
                this._textArea.setAttribute('type', 'text');
                this._textArea.setAttribute('autocorrect', 'off');
                this._textArea.setAttribute('autocapitalize', 'off');
diff --git a/loleaflet/src/layer/tile/TileLayer.js 
b/loleaflet/src/layer/tile/TileLayer.js
index d7f74a773..9313c59be 100644
--- a/loleaflet/src/layer/tile/TileLayer.js
+++ b/loleaflet/src/layer/tile/TileLayer.js
@@ -26,12 +26,18 @@ function hex2string(inData)
 }
 
 L.Compatibility = {
+       stripHTML: function(html) { // grim.
+               var tmp = document.createElement('div');
+               tmp.innerHTML = html;
+               return tmp.textContent || tmp.innerText || '';
+       },
+
        clipboardSet: function (event, text) {
                if (event.clipboardData) { // Standard
-                       event.clipboardData.setData('text/plain', text);
+                       event.clipboardData.setData('text/html', text);
                }
-               else if (window.clipboardData) { // IE 11
-                       window.clipboardData.setData('Text', text);
+               else if (window.clipboardData) { // IE 11 - poor clipboard API
+                       window.clipboardData.setData('Text', 
this.stripHTML(text));
                }
        }
 };
@@ -521,6 +527,9 @@ L.TileLayer = L.GridLayer.extend({
                else if (textMsg.startsWith('editor:')) {
                        this._updateEditor(textMsg);
                }
+               else if (textMsg.startsWith('pasteresult:')) {
+                       this._pasteResult(textMsg);
+               }
                else if (textMsg.startsWith('validitylistbutton:')) {
                        this._onValidityListButtonMsg(textMsg);
                }
@@ -1268,7 +1277,7 @@ L.TileLayer = L.GridLayer.extend({
                                clearTimeout(this._selectionContentRequest);
                        }
                        this._selectionContentRequest = 
setTimeout(L.bind(function () {
-                               this._map._socket.sendMessage('gettextselection 
mimetype=text/plain;charset=utf-8');}, this), 100);
+                               this._map._socket.sendMessage('gettextselection 
mimetype=text/html');}, this), 100);
                }
                this._onUpdateTextSelection();
        },
@@ -1308,6 +1317,7 @@ L.TileLayer = L.GridLayer.extend({
 
        _onTextSelectionContentMsg: function (textMsg) {
                this._selectionTextContent = textMsg.substr(22);
+               
this._map._clipboardContainer.setValue(this._selectionTextContent);
        },
 
        _updateScrollOnCellSelection: function (oldSelection, newSelection) {
@@ -2691,45 +2701,113 @@ L.TileLayer = L.GridLayer.extend({
                this._dataTransferToDocument(e.dataTransfer, /* preferInternal 
= */ false);
        },
 
-       _dataTransferToDocument: function (dataTransfer, preferInternal) {
-               // for the paste, we might prefer the internal LOK's copy/paste
-               if (preferInternal === true) {
-                       var pasteString = dataTransfer.getData('text/plain');
-                       if (!pasteString) {
-                               pasteString = dataTransfer.getData('Text'); // 
IE 11
+       _getMetaOrigin: function (html) {
+               var match = '<meta name="origin" content="';
+               var start = html.indexOf(match);
+               if (start < 0) {
+                       return '';
+               }
+               var end = html.indexOf('"', start + match.length);
+               if (end < 0) {
+                       return '';
+               }
+               var meta = html.substring(start + match.length, end);
+
+               // quick sanity checks that it one of ours.
+               if (meta.indexOf('%2Fclipboard%3FWOPISrc%3D') > 0 &&
+                   meta.indexOf('%26ServerId%3D') > 0 &&
+                   meta.indexOf('%26ViewId%3D') > 0 &&
+                   meta.indexOf('%26Tag%3D') > 0)
+                       return decodeURIComponent(meta);
+               else
+                       console.log('Mis-understood foreign origin: "' + meta + 
'"');
+               return '';
+       },
+
+       // Sometimes our smart-paste fails & we need to try again.
+       _pasteResult : function(textMsg)
+       {
+               textMsg = textMsg.substring('pasteresult:'.length + 1);
+               console.log('Paste state: ' + textMsg);
+               if (textMsg == 'fallback') {
+                       if (this._pasteFallback != null) {
+                               console.log('Paste failed- falling back to 
HTML');
+                               
this._map._socket.sendMessage(this._pasteFallback);
+                       } else {
+                               console.log('No paste fallback present.');
                        }
+               }
 
-                       if (pasteString && pasteString === 
this._selectionTextHash) {
-                               this._map._socket.sendMessage('uno .uno:Paste');
-                               return;
-                       }
+               this._pasteFallback = null;
+       },
+
+       _dataTransferToDocument: function (dataTransfer, preferInternal) {
+
+               // Look for our HTML meta magic.
+               //   cf. ClientSession.cpp /textselectioncontent:/
+               var pasteHtml = dataTransfer.getData('text/html');
+               var meta = this._getMetaOrigin(pasteHtml);
+               var id = this._map.options.webserver + 
this._map.options.serviceRoot +
+                   '/clipboard?WOPISrc='+ 
encodeURIComponent(this._map.options.doc) +
+                   '&ServerId=' + this._map._socket.WSDServer.Id + '&ViewId=' 
+ this._viewId;
+
+               // for the paste, we might prefer the internal LOK's copy/paste
+               if (meta.startsWith(id) && preferInternal === true) {
+                       // Home from home: short-circuit internally.
+                       console.log('short-circuit, internal paste');
+                       this._map._socket.sendMessage('uno .uno:Paste');
+                       return;
                }
 
-               var types = dataTransfer.types;
+               console.log('Resetting paste fallback');
+               this._pasteFallback = null;
+
+               // Suck HTML content out of dataTransfer now while it feels 
like working.
+               var content = this._readContentSync(dataTransfer);
 
-               // first try to transfer images
-               // TODO if we have both Files and a normal mimetype, should we 
handle
-               // both, or prefer one or the other?
-               for (var t = 0; t < types.length; ++t) {
-                       if (types[t] === 'Files') {
-                               var files = dataTransfer.files;
-                               for (var f = 0; f < files.length; ++f) {
-                                       var file = files[f];
-                                       if (file.type.match(/image.*/)) {
-                                               var reader = new FileReader();
-                                               reader.onload = 
this._onFileLoadFunc(file);
-                                               reader.readAsArrayBuffer(file);
+               // Images get a look in only if we have no content and are async
+               if (content == null && pasteHtml == '')
+               {
+                       var types = dataTransfer.types;
+
+                       console.log('Attempting to paste image(s)');
+
+                       // first try to transfer images
+                       // TODO if we have both Files and a normal mimetype, 
should we handle
+                       // both, or prefer one or the other?
+                       for (var t = 0; t < types.length; ++t) {
+                               console.log('\ttype' + types[t]);
+                               if (types[t] === 'Files') {
+                                       var files = dataTransfer.files;
+                                       for (var f = 0; f < files.length; ++f) {
+                                               var file = files[f];
+                                               if (file.type.match(/image.*/)) 
{
+                                                       var reader = new 
FileReader();
+                                                       reader.onload = 
this._onFileLoadFunc(file);
+                                                       
reader.readAsArrayBuffer(file);
+                                               }
                                        }
                                }
                        }
+                       return;
                }
 
-               // now try various mime types
+               if (content != null) {
+                       console.log('Normal HTML, so smart paste not possible');
+                       this._map._socket.sendMessage(content);
+                       this._pasteFallback = null;
+               } else {
+                       console.log('Nothing we can paste on the clipboard');
+               }
+       },
+
+       _readContentSync: function(dataTransfer) {
+               // Try various content mime types
                var mimeTypes;
                if (this._docType === 'spreadsheet') {
-                       // FIXME apparently we cannot paste the text/html or 
text/rtf as
-                       // produced by LibreOffice in Calc from some reason
                        mimeTypes = [
+                               ['text/rtf', 'text/rtf'],
+                               ['text/html', 'text/html'],
                                ['text/plain', 'text/plain;charset=utf-8'],
                                ['Text', 'text/plain;charset=utf-8']
                        ];
@@ -2752,15 +2830,15 @@ L.TileLayer = L.GridLayer.extend({
                        ];
                }
 
+               var types = dataTransfer.types;
                for (var i = 0; i < mimeTypes.length; ++i) {
-                       for (t = 0; t < types.length; ++t) {
+                       for (var t = 0; t < types.length; ++t) {
                                if (mimeTypes[i][0] === types[t]) {
-                                       var blob = new Blob(['paste mimetype=' 
+ mimeTypes[i][1] + '\n', dataTransfer.getData(types[t])]);
-                                       this._map._socket.sendMessage(blob);
-                                       return;
+                                       return new Blob(['paste mimetype=' + 
mimeTypes[i][1] + '\n', dataTransfer.getData(types[t])]);
                                }
                        }
                }
+               return null;
        },
 
        _onFileLoadFunc: function(file) {
diff --git a/loleaflet/src/map/handler/Map.Keyboard.js 
b/loleaflet/src/map/handler/Map.Keyboard.js
index d13cc37e2..8097f9c74 100644
--- a/loleaflet/src/map/handler/Map.Keyboard.js
+++ b/loleaflet/src/map/handler/Map.Keyboard.js
@@ -531,7 +531,7 @@ L.Map.Keyboard = L.Handler.extend({
                case 91: // Left Cmd (Safari)
                case 93: // Right Cmd (Safari)
                        // we prepare for a copy or cut event
-                       
this._map._clipboardContainer.setValue(window.getSelection().toString());
+                       this._map._clipboardContainer.resetToSelection();
                        this._map.focus();
                        this._map._clipboardContainer.select();
                        return true;
diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp
index 6838e24ff..b8363ab98 100644
--- a/wsd/ClientSession.cpp
+++ b/wsd/ClientSession.cpp
@@ -14,6 +14,7 @@
 #include <fstream>
 #include <sstream>
 #include <memory>
+#include <unordered_map>
 
 #include <Poco/Net/HTTPResponse.h>
 #include <Poco/StreamCopier.h>
@@ -35,10 +36,14 @@ using namespace LOOLProtocol;
 using Poco::Path;
 using Poco::StringTokenizer;
 
+static std::mutex SessionMapMutex;
+static std::unordered_map<std::string, std::weak_ptr<ClientSession>> 
SessionMap;
+
 ClientSession::ClientSession(const std::string& id,
                              const std::shared_ptr<DocumentBroker>& docBroker,
                              const Poco::URI& uriPublic,
-                             const bool readOnly) :
+                             const bool readOnly,
+                             const std::string& hostNoTrust) :
     Session("ToClient-" + id, id, readOnly),
     _docBroker(docBroker),
     _uriPublic(uriPublic),
@@ -52,16 +57,93 @@ ClientSession::ClientSession(const std::string& id,
     _tileHeightPixel(0),
     _tileWidthTwips(0),
     _tileHeightTwips(0),
+    _kitViewId(-1),
+    _hostNoTrust(hostNoTrust),
     _isTextDocument(false)
 {
     const size_t curConnections = ++LOOLWSD::NumConnections;
     LOG_INF("ClientSession ctor [" << getName() << "], current number of 
connections: " << curConnections);
 }
 
+// Can't take a reference in the constructor.
+void ClientSession::construct()
+{
+    std::unique_lock<std::mutex> lock(SessionMapMutex);
+    SessionMap[getId()] = shared_from_this();
+}
+
 ClientSession::~ClientSession()
 {
     const size_t curConnections = --LOOLWSD::NumConnections;
     LOG_INF("~ClientSession dtor [" << getName() << "], current number of 
connections: " << curConnections);
+
+    std::unique_lock<std::mutex> lock(SessionMapMutex);
+    SessionMap.erase(getId());
+}
+
+std::string ClientSession::getURIAndUpdateClipboardHash()
+{
+    std::string hash = Util::rng::getHexString(16);
+    {
+        std::unique_lock<std::mutex> lock(SessionMapMutex);
+        _clipboardKey = hash;
+        LOG_TRC("Clipboard key on [" << getId() << "] set to " << 
_clipboardKey);
+   }
+
+    std::string encodedFrom;
+    Poco::URI wopiSrc = getDocumentBroker()->getPublicUri();
+    wopiSrc.setQueryParameters(Poco::URI::QueryParameters());
+
+    std::string encodeChars = ",/?:@&=+$#"; // match JS encodeURIComponent
+    Poco::URI::encode(wopiSrc.toString(), encodeChars, encodedFrom);
+
+    std::string proto = (LOOLWSD::isSSLEnabled() || 
LOOLWSD::isSSLTermination()) ? "https://"; : "http://";;
+    std::string meta = proto + _hostNoTrust +
+        "/clipboard?WOPISrc=" + encodedFrom +
+        "&ServerId=" + LOOLWSD::HostIdentifier +
+        "&ViewId=" + std::to_string(getKitViewId()) +
+        "&Tag=" + hash;
+
+    std::string metaEncoded;
+    Poco::URI::encode(meta, encodeChars, metaEncoded);
+
+    return metaEncoded;
+}
+
+// called infrequently
+std::shared_ptr<ClientSession> ClientSession::getByClipboardHash(std::string 
&key)
+{
+    std::unique_lock<std::mutex> lock(SessionMapMutex);
+    for (auto &it : SessionMap)
+    {
+        auto session = it.second.lock();
+        if (session && session->_clipboardKey == key)
+        {
+            if (session->_wopiFileInfo &&
+                session->_wopiFileInfo->getDisableCopy())
+            {
+                LOG_WRN("Attempting copy from disabled session " << key);
+                break;
+            }
+            else
+                return session;
+        }
+    }
+    return std::shared_ptr<ClientSession>();
+}
+
+void ClientSession::addClipboardSocket(const std::shared_ptr<StreamSocket> 
&socket)
+{
+    // Move the socket into our DocBroker.
+    auto docBroker = getDocumentBroker();
+    docBroker->addSocketToPoll(socket);
+
+    LOG_TRC("Session [" << getId() << "] sending getbinaryselection");
+    const std::string gettext = "getbinaryselection 
mimetype=application/x-openoffice-embed-source-xml";
+    docBroker->forwardToChild(getId(), gettext);
+
+    // TESTME: onerror / socket cleanup.
+    _clipSockets.push_back(socket);
 }
 
 void ClientSession::handleIncomingMessage(SocketDisposition &disposition)
@@ -949,6 +1031,60 @@ bool ClientSession::handleKitToClientMessage(const char* 
buffer, const int lengt
                 }
             }
         }
+    } else if (tokens[0] == "textselectioncontent:") {
+
+        // Insert our meta origin if we can
+        payload->rewriteDataBody([=](std::vector<char>& data) {
+                size_t pos = Util::findInVector(data, "<meta 
name=\"generator\" content=\"");
+
+                // cf. TileLayer.js /_dataTransferToDocument/
+                if (pos != std::string::npos) // assume text/html
+                {
+                    std::string meta = getURIAndUpdateClipboardHash();
+                    std::string origin = "<meta name=\"origin\" content=\"" + 
meta + "\"/>\n";
+                    data.insert(data.begin() + pos, origin.begin(), 
origin.end());
+                    return true;
+                }
+                else
+                {
+                    LOG_DBG("Missing generator in textselectioncontent");
+                    return false;
+                }
+            });
+        return forwardToClient(payload);
+
+    } else if (tokens[0] == "binaryselectioncontent:") {
+
+        // for now just for remote sockets.
+        LOG_TRC("Got binary selection content to send to " << 
_clipSockets.size() << "sockets");
+        size_t header;
+        for (header = 0; header < payload->size();)
+            if (payload->data()[header++] == '\n')
+                break;
+        if (header < payload->size())
+        {
+            for (auto it : _clipSockets)
+            {
+                std::ostringstream oss;
+                oss << "HTTP/1.1 200 OK\r\n"
+                    << "Last-Modified: " << 
Poco::DateTimeFormatter::format(Poco::Timestamp(), 
Poco::DateTimeFormat::HTTP_FORMAT) << "\r\n"
+                    << "User-Agent: " << WOPI_AGENT_STRING << "\r\n"
+                    << "Content-Length: " << (payload->size() - header) << 
"\r\n"
+                    << "Content-Type: application/octet-stream\r\n"
+                    << "X-Content-Type-Options: nosniff\r\n"
+                    << "\r\n";
+                oss.write(&payload->data()[header], payload->size() - header);
+                auto socket = it.lock();
+                socket->setSocketBufferSize(std::min(payload->size() + 256,
+                                                     
size_t(Socket::MaximumSendBufferSize)));
+                socket->send(oss.str());
+                socket->shutdown();
+                LOG_INF("Sent clipboard content.");
+            }
+        }
+        else
+            LOG_DBG("Odd - no binary selection content");
+        _clipSockets.clear();
     }
 
     if (!isDocPasswordProtected())
@@ -985,6 +1121,11 @@ bool ClientSession::handleKitToClientMessage(const char* 
buffer, const int lengt
                 {
                     _isTextDocument = docType.find("text") != 
std::string::npos;
                 }
+
+                // Store our Kit ViewId
+                int viewId = -1;
+                if(getTokenInteger(token, "viewid", viewId))
+                    _kitViewId = viewId;
             }
 
             // Forward the status response to the client.
@@ -1260,7 +1401,16 @@ void ClientSession::dumpState(std::ostream& os)
     os << "\t\tisReadOnly: " << isReadOnly()
        << "\n\t\tisDocumentOwner: " << _isDocumentOwner
        << "\n\t\tisAttached: " << _isAttached
-       << "\n\t\tkeyEvents: " << _keyEvents;
+       << "\n\t\tkeyEvents: " << _keyEvents
+//       << "\n\t\tvisibleArea: " << _clientVisibleArea
+       << "\n\t\tclientSelectedPart: " << _clientSelectedPart
+       << "\n\t\ttile size Pixel: " << _tileWidthPixel << "x" << 
_tileHeightPixel
+       << "\n\t\ttile size Twips: " << _tileWidthTwips << "x" << 
_tileHeightTwips
+       << "\n\t\tkit ViewId: " << _kitViewId
+       << "\n\t\thost (un-trusted): " << _hostNoTrust
+       << "\n\t\tisTextDocument: " << _isTextDocument
+       << "\n\t\tclipboardKey: " << _clipboardKey
+       << "\n\t\tclip sockets: " << _clipSockets.size();
 
     std::shared_ptr<StreamSocket> socket = getSocket().lock();
     if (socket)
diff --git a/wsd/ClientSession.hpp b/wsd/ClientSession.hpp
index 8db5b3718..283ca1b83 100644
--- a/wsd/ClientSession.hpp
+++ b/wsd/ClientSession.hpp
@@ -32,10 +32,14 @@ public:
     ClientSession(const std::string& id,
                   const std::shared_ptr<DocumentBroker>& docBroker,
                   const Poco::URI& uriPublic,
-                  const bool isReadOnly = false);
-
+                  const bool isReadOnly,
+                  const std::string& hostNoTrust);
+    void construct();
     virtual ~ClientSession();
 
+    /// Lookup any session by id.
+    static std::shared_ptr<ClientSession> getById(const std::string &id);
+
     void handleIncomingMessage(SocketDisposition &) override;
 
     void setReadOnly() override;
@@ -54,6 +58,9 @@ public:
     /// Handle kit-to-client message.
     bool handleKitToClientMessage(const char* data, const int size);
 
+    /// Integer id of the view in the kit process, or -1 if unknown
+    int getKitViewId() const { return _kitViewId; }
+
     // sendTextFrame that takes std::string and string literal.
     using Session::sendTextFrame;
 
@@ -133,8 +140,17 @@ public:
     void resetWireIdMap();
 
     bool isTextDocument() const { return _isTextDocument; }
+
+    /// Find clipboard for session
+    static std::shared_ptr<ClientSession> getByClipboardHash(std::string &key);
+
+    void addClipboardSocket(const std::shared_ptr<StreamSocket> &socket);
+
 private:
 
+    /// Create URI for transient clipboard content.
+    std::string getURIAndUpdateClipboardHash();
+
     /// SocketHandler: disconnection event.
     void onDisconnect() override;
     /// Does SocketHandler: have data or timeouts to setup.
@@ -212,9 +228,18 @@ private:
     int _tileWidthTwips;
     int _tileHeightTwips;
 
+    /// The integer id of the view in the Kit process
+    int _kitViewId;
+
+    /// Un-trusted hostname of our service from the client
+    const std::string _hostNoTrust;
+
     /// Client is using a text document?
     bool _isTextDocument;
 
+    /// Transient clipboard identifier - protected by SessionMapMutex
+    std::string _clipboardKey;
+
     /// TileID's of the sent tiles. Push by sending and pop by tileprocessed 
message from the client.
     std::list<std::pair<std::string, std::chrono::steady_clock::time_point>> 
_tilesOnFly;
 
@@ -223,6 +248,9 @@ private:
 
     /// Store wireID's of the sent tiles inside the actual visible area
     std::map<std::string, TileWireId> _oldWireIds;
+
+    /// Sockets to send binary selection content to
+    std::vector<std::weak_ptr<StreamSocket>> _clipSockets;
 };
 
 
diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp
index d777b38bf..e7bdbafe7 100644
--- a/wsd/LOOLWSD.cpp
+++ b/wsd/LOOLWSD.cpp
@@ -685,7 +685,8 @@ inline std::string getAdminURI(const 
Poco::Util::LayeredConfiguration &config)
 
 #endif // MOBILEAPP
 
-std::atomic<unsigned> LOOLWSD::NextSessionId;
+std::atomic<uint64_t> LOOLWSD::NextSessionId;
+
 #ifndef KIT_IN_PROCESS
 std::atomic<int> LOOLWSD::ForKitWritePipe(-1);
 std::atomic<int> LOOLWSD::ForKitProcId(-1);
@@ -1740,7 +1741,8 @@ static std::shared_ptr<ClientSession> 
createNewClientSession(const WebSocketHand
                                                              const 
std::string& id,
                                                              const Poco::URI& 
uriPublic,
                                                              const 
std::shared_ptr<DocumentBroker>& docBroker,
-                                                             const bool 
isReadOnly)
+                                                             const bool 
isReadOnly,
+                                                             const 
std::string& hostNoTrust)
 {
     LOG_CHECK_RET(docBroker && "Null docBroker instance", nullptr);
     try
@@ -1756,7 +1758,10 @@ static std::shared_ptr<ClientSession> 
createNewClientSession(const WebSocketHand
         // In case of WOPI, if this session is not set as readonly, it might 
be set so
         // later after making a call to WOPI host which tells us the 
permission on files
         // (UserCanWrite param).
-        return std::make_shared<ClientSession>(id, docBroker, uriPublic, 
isReadOnly);
+        auto session = std::make_shared<ClientSession>(id, docBroker, 
uriPublic, isReadOnly, hostNoTrust);
+        session->construct();
+
+        return session;
     }
     catch (const std::exception& exc)
     {
@@ -2406,7 +2411,8 @@ private:
                 // Load the document.
                 // TODO: Move to DocumentBroker.
                 const bool isReadOnly = true;
-                std::shared_ptr<ClientSession> clientSession = 
createNewClientSession(nullptr, _id, uriPublic, docBroker, isReadOnly);
+                std::shared_ptr<ClientSession> clientSession = 
createNewClientSession(
+                    nullptr, _id, uriPublic, docBroker, isReadOnly, 
"nocliphost");
                 if (clientSession)
                 {
                     disposition.setMove([docBroker, clientSession, format]
@@ -2661,7 +2667,11 @@ private:
             std::shared_ptr<DocumentBroker> docBroker = 
findOrCreateDocBroker(ws, url, docKey, _id, uriPublic);
             if (docBroker)
             {
-                std::shared_ptr<ClientSession> clientSession = 
createNewClientSession(&ws, _id, uriPublic, docBroker, isReadOnly);
+                // We can send this back to whomever sent it to us though.
+                const std::string hostNoTrust = (LOOLWSD::ServerName.empty() ? 
request.getHost() : LOOLWSD::ServerName);
+
+                std::shared_ptr<ClientSession> clientSession = 
createNewClientSession(&ws, _id, uriPublic,
+                                                                               
       docBroker, isReadOnly, hostNoTrust);
                 if (clientSession)
                 {
                     // Transfer the client socket to the DocumentBroker when 
we get back to the poll:
diff --git a/wsd/LOOLWSD.hpp b/wsd/LOOLWSD.hpp
index 6928cb6f9..83f950bad 100644
--- a/wsd/LOOLWSD.hpp
+++ b/wsd/LOOLWSD.hpp
@@ -45,7 +45,7 @@ public:
 
     // An Application is a singleton anyway,
     // so just keep these as statics.
-    static std::atomic<unsigned> NextSessionId;
+    static std::atomic<uint64_t> NextSessionId;
     static unsigned int NumPreSpawnedChildren;
     static bool NoCapsForKit;
     static bool NoSeccomp;
_______________________________________________
Libreoffice-commits mailing list
[email protected]
https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits

Reply via email to