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
