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