2016-05-16 13:37:02 +02:00
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
2023-10-30 22:58:54 +01:00
* Copyright the Collabora Online contributors .
*
* SPDX - License - Identifier : MPL - 2.0
2023-11-09 19:23:00 +01:00
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License , v . 2.0 . If a copy of the MPL was not distributed with this
* file , You can obtain one at http : //mozilla.org/MPL/2.0/.
2016-05-16 13:37:02 +02:00
*/
2017-12-20 14:06:26 +01:00
# include <config.h>
2016-05-16 13:37:02 +02:00
2017-03-08 17:38:22 +01:00
# include "ClientSession.hpp"
2021-05-02 18:19:21 +02:00
# include <ios>
2018-06-13 15:04:09 +02:00
# include <sstream>
2024-01-05 08:16:13 +01:00
# include <string>
# include <string_view>
2018-10-01 18:11:25 +02:00
# include <memory>
2019-05-29 17:26:16 +02:00
# include <unordered_map>
loolwsd: include cleanup and organization
A source file (.cpp) must include its own header first.
This insures that the header is self-contained and
doesn't depend on arbitrary (and accidental) includes
before it to compile.
Furthermore, system headers should go next, followed by
C then C++ headers, then libraries (Poco, etc) and, finally,
project headers come last.
This makes sure that headers and included in the same dependency
order to avoid side-effects. For example, Poco should never rely on
anything from our project in the same way that a C header should
never rely on anything in C++, Poco, or project headers.
Also, includes ought to be sorted where possible, to improve
readability and avoid accidental duplicates (of which there
were a few).
Change-Id: I62cc1343e4a091d69195e37ed659dba20cfcb1ef
Reviewed-on: https://gerrit.libreoffice.org/25262
Reviewed-by: Ashod Nakashian <ashnakash@gmail.com>
Tested-by: Ashod Nakashian <ashnakash@gmail.com>
2016-05-21 16:23:07 +02:00
2023-02-23 18:30:25 +01:00
# include <Poco/Base64Decoder.h>
2017-04-24 03:25:09 +02:00
# include <Poco/Net/HTTPResponse.h>
2018-07-16 07:04:24 +02:00
# include <Poco/StreamCopier.h>
2016-05-16 13:37:02 +02:00
# include <Poco/URI.h>
2016-12-21 21:37:22 +01:00
# include "DocumentBroker.hpp"
2021-11-18 13:08:14 +01:00
# include "COOLWSD.hpp"
2024-05-08 12:04:54 +02:00
# include "FileServer.hpp"
2017-12-20 14:06:26 +01:00
# include <common/Common.hpp>
2022-08-27 18:32:42 +02:00
# include <common/JsonUtil.hpp>
2017-12-20 14:06:26 +01:00
# include <common/Log.hpp>
# include <common/Protocol.hpp>
2019-07-04 11:50:33 +02:00
# include <common/Clipboard.hpp>
2017-12-20 14:06:26 +01:00
# include <common/Session.hpp>
2021-06-01 12:21:09 +02:00
# include <common/TraceEvent.hpp>
2017-12-20 14:06:26 +01:00
# include <common/Util.hpp>
2022-07-05 14:03:14 +02:00
# include <common/CommandControl.hpp>
2020-10-26 13:55:54 +01:00
# if !MOBILEAPP
# include <net/HttpHelper.hpp>
# endif
2021-07-11 21:24:46 +02:00
2021-11-18 13:08:14 +01:00
using namespace COOLProtocol ;
2016-05-17 01:05:22 +02:00
2023-12-04 10:12:15 +01:00
static constexpr float TILES_ON_FLY_MIN_UPPER_LIMIT = 10.0 ;
2021-11-03 15:39:37 +01:00
static constexpr int SYNTHETIC_COOL_PID_OFFSET = 10000000 ;
2021-06-22 11:16:51 +02:00
2017-01-22 03:38:11 +01:00
using Poco : : Path ;
2016-05-17 01:05:22 +02:00
2020-05-13 00:52:25 +02:00
// rotates regularly
const int ClipboardTokenLengthBytes = 16 ;
// home-use, disabled by default.
const int ProxyAccessTokenLengthBytes = 32 ;
2020-01-02 22:11:54 +01:00
static std : : mutex GlobalSessionMapMutex ;
static std : : unordered_map < std : : string , std : : weak_ptr < ClientSession > > GlobalSessionMap ;
2019-05-29 17:26:16 +02:00
2024-01-03 11:20:15 +01:00
namespace
{
void logSyntaxErrorDetails ( const StringVector & tokens , const std : : string & firstLine )
{
LOG_WRN ( " Invalid syntax for ' " < < tokens [ 0 ] < < " ' message: [ " < < firstLine < < ' ] ' ) ;
}
}
2020-03-06 18:43:46 +01:00
ClientSession : : ClientSession (
const std : : shared_ptr < ProtocolHandlerInterface > & ws ,
const std : : string & id ,
const std : : shared_ptr < DocumentBroker > & docBroker ,
const Poco : : URI & uriPublic ,
const bool readOnly ,
2020-05-12 22:10:56 +02:00
const RequestDetails & requestDetails ) :
2020-03-06 18:43:46 +01:00
Session ( ws , " ToClient- " + id , id , readOnly ) ,
2016-10-16 19:20:49 +02:00
_docBroker ( docBroker ) ,
2016-10-16 18:40:52 +02:00
_uriPublic ( uriPublic ) ,
2020-06-20 20:09:21 +02:00
_auth ( Authorization : : create ( uriPublic ) ) ,
2016-11-08 14:37:28 +01:00
_isDocumentOwner ( false ) ,
2019-07-04 11:50:33 +02:00
_state ( SessionState : : DETACHED ) ,
2023-03-04 20:18:58 +01:00
_lastStateTime ( std : : chrono : : steady_clock : : now ( ) ) ,
2018-05-30 19:35:13 +02:00
_keyEvents ( 1 ) ,
_clientVisibleArea ( 0 , 0 , 0 , 0 ) ,
2020-07-08 03:40:13 +02:00
_splitX ( 0 ) ,
_splitY ( 0 ) ,
2018-05-31 20:20:09 +02:00
_clientSelectedPart ( - 1 ) ,
2022-08-30 07:27:44 +02:00
_clientSelectedMode ( 0 ) ,
2018-05-30 19:35:13 +02:00
_tileWidthPixel ( 0 ) ,
_tileHeightPixel ( 0 ) ,
_tileWidthTwips ( 0 ) ,
2018-06-19 16:15:37 +02:00
_tileHeightTwips ( 0 ) ,
2019-05-29 17:26:16 +02:00
_kitViewId ( - 1 ) ,
2020-05-12 22:10:56 +02:00
_serverURL ( requestDetails ) ,
2023-02-24 09:14:03 +01:00
_isTextDocument ( false ) ,
2023-09-03 14:53:24 +02:00
_thumbnailSession ( false ) ,
_canonicalViewId ( 0 )
2016-05-17 02:49:36 +02:00
{
2021-11-18 13:08:14 +01:00
const std : : size_t curConnections = + + COOLWSD : : NumConnections ;
2020-01-18 22:04:26 +01:00
LOG_INF ( " ClientSession ctor [ " < < getName ( ) < < " ] for URI: [ " < < _uriPublic . toString ( )
< < " ], current number of connections: " < < curConnections ) ;
2019-06-21 13:35:17 +02:00
// populate with random values.
2023-08-22 21:30:29 +02:00
for ( size_t i = 0 ; i < N_ELEMENTS ( _clipboardKeys ) ; + + i )
2019-06-22 22:23:12 +02:00
rotateClipboardKey ( false ) ;
2019-07-04 11:50:33 +02:00
2021-06-22 11:16:51 +02:00
// Emit metadata Trace Events for the synthetic pid used for the Trace Events coming in from the
2021-11-03 15:31:18 +01:00
// client's cool, and for its dummy thread.
2021-06-22 11:16:51 +02:00
TraceEvent : : emitOneRecordingIfEnabled ( " { \" name \" : \" process_name \" , \" ph \" : \" M \" , \" args \" :{ \" name \" : \" "
2021-11-03 15:44:08 +01:00
" cool- " + id
2021-06-22 11:16:51 +02:00
+ " \" }, \" pid \" : "
2021-11-03 15:39:37 +01:00
+ std : : to_string ( getpid ( ) + SYNTHETIC_COOL_PID_OFFSET )
2021-06-22 11:16:51 +02:00
+ " , \" tid \" :1}, \n " ) ;
TraceEvent : : emitOneRecordingIfEnabled ( " { \" name \" : \" thread_name \" , \" ph \" : \" M \" , \" args \" :{ \" name \" : \" JS \" }, \" pid \" : "
2021-11-03 15:39:37 +01:00
+ std : : to_string ( getpid ( ) + SYNTHETIC_COOL_PID_OFFSET )
2021-06-22 11:16:51 +02:00
+ " , \" tid \" :1}, \n " ) ;
2016-05-17 02:49:36 +02:00
}
2019-05-29 17:26:16 +02:00
// Can't take a reference in the constructor.
void ClientSession : : construct ( )
{
2020-01-02 22:11:54 +01:00
std : : unique_lock < std : : mutex > lock ( GlobalSessionMapMutex ) ;
2020-03-06 18:43:46 +01:00
MessageHandlerInterface : : initialize ( ) ;
GlobalSessionMap [ getId ( ) ] = client_from_this ( ) ;
2019-05-29 17:26:16 +02:00
}
2016-05-17 01:05:22 +02:00
ClientSession : : ~ ClientSession ( )
{
2021-11-18 13:08:14 +01:00
const std : : size_t curConnections = - - COOLWSD : : NumConnections ;
2017-03-26 06:50:51 +02:00
LOG_INF ( " ~ClientSession dtor [ " < < getName ( ) < < " ], current number of connections: " < < curConnections ) ;
2019-05-29 17:26:16 +02:00
2020-01-02 22:11:54 +01:00
std : : unique_lock < std : : mutex > lock ( GlobalSessionMapMutex ) ;
GlobalSessionMap . erase ( getId ( ) ) ;
2019-05-29 17:26:16 +02:00
}
2019-07-04 11:50:33 +02:00
void ClientSession : : setState ( SessionState newState )
{
2022-06-04 15:16:04 +02:00
LOG_TRC ( " transition from " < < name ( _state ) < < " to " < < name ( newState ) ) ;
2019-08-06 20:49:22 +02:00
// we can get incoming messages while our disconnection is in transit.
if ( _state = = SessionState : : WAIT_DISCONNECT )
{
if ( newState ! = SessionState : : WAIT_DISCONNECT )
2022-04-05 13:50:18 +02:00
LOG_WRN ( " Unusual race - attempts to transition from " < < name ( _state ) < < " to "
< < name ( newState ) ) ;
2019-08-06 20:49:22 +02:00
return ;
}
2019-07-04 11:50:33 +02:00
switch ( newState )
{
case SessionState : : DETACHED :
assert ( _state = = SessionState : : DETACHED ) ;
break ;
case SessionState : : LOADING :
assert ( _state = = SessionState : : DETACHED ) ;
break ;
case SessionState : : LIVE :
assert ( _state = = SessionState : : LIVE | |
_state = = SessionState : : LOADING ) ;
break ;
case SessionState : : WAIT_DISCONNECT :
assert ( _state = = SessionState : : LOADING | |
_state = = SessionState : : LIVE ) ;
break ;
}
_state = newState ;
_lastStateTime = std : : chrono : : steady_clock : : now ( ) ;
}
bool ClientSession : : disconnectFromKit ( )
{
assert ( _state ! = SessionState : : WAIT_DISCONNECT ) ;
auto docBroker = getDocumentBroker ( ) ;
2024-05-17 16:39:55 +02:00
if ( docBroker & & ( _state = = SessionState : : LIVE | | _state = = SessionState : : LOADING ) )
2019-07-04 11:50:33 +02:00
{
setState ( SessionState : : WAIT_DISCONNECT ) ;
// handshake nicely; so wait for 'disconnected'
2023-03-11 19:18:51 +01:00
LOG_TRC ( " Sending 'disconnect' command to session " < < getId ( ) ) ;
2022-11-27 04:23:17 +01:00
docBroker - > forwardToChild ( client_from_this ( ) , " disconnect " ) ;
2019-07-04 11:50:33 +02:00
return false ;
}
return true ; // just get on with it
}
2019-10-08 11:23:29 +02:00
// Allow 20secs for the clipboard and disconnection to come.
2019-07-04 11:50:33 +02:00
bool ClientSession : : staleWaitDisconnect ( const std : : chrono : : steady_clock : : time_point & now )
{
if ( _state ! = SessionState : : WAIT_DISCONNECT )
return false ;
return std : : chrono : : duration_cast < std : : chrono : : seconds > ( now - _lastStateTime ) . count ( ) > = 20 ;
}
2019-06-22 22:23:12 +02:00
void ClientSession : : rotateClipboardKey ( bool notifyClient )
2019-05-29 17:26:16 +02:00
{
2019-08-06 17:26:07 +02:00
if ( _state = = SessionState : : WAIT_DISCONNECT )
2019-07-04 11:50:33 +02:00
return ;
2019-06-21 13:35:17 +02:00
_clipboardKeys [ 1 ] = _clipboardKeys [ 0 ] ;
2024-02-05 12:50:28 +01:00
_clipboardKeys [ 0 ] = Util : : rng : : getHexString (
2020-05-13 00:52:25 +02:00
ClipboardTokenLengthBytes ) ;
2019-06-21 13:35:17 +02:00
LOG_TRC ( " Clipboard key on [ " < < getId ( ) < < " ] set to " < < _clipboardKeys [ 0 ] < <
" last was " < < _clipboardKeys [ 1 ] ) ;
if ( notifyClient )
2019-06-22 22:23:12 +02:00
sendTextFrame ( " clipboardkey: " + _clipboardKeys [ 0 ] ) ;
2019-06-21 13:35:17 +02:00
}
2019-06-21 18:48:57 +02:00
std : : string ClientSession : : getClipboardURI ( bool encode )
2019-06-21 13:35:17 +02:00
{
2021-07-25 14:49:07 +02:00
if ( _wopiFileInfo & & _wopiFileInfo - > getDisableCopy ( ) )
return std : : string ( ) ;
2022-10-25 13:42:46 +02:00
return createPublicURI ( " clipboard " , _clipboardKeys [ 0 ] , encode ) ;
}
std : : string ClientSession : : createPublicURI ( const std : : string & subPath , const std : : string & tag , bool encode )
{
2019-05-29 17:26:16 +02:00
Poco : : URI wopiSrc = getDocumentBroker ( ) - > getPublicUri ( ) ;
wopiSrc . setQueryParameters ( Poco : : URI : : QueryParameters ( ) ) ;
2022-10-25 13:37:34 +02:00
const std : : string encodedFrom = Util : : encodeURIComponent ( wopiSrc . toString ( ) ) ;
2019-05-29 17:26:16 +02:00
2020-05-06 18:02:51 +02:00
std : : string meta = _serverURL . getSubURLForEndpoint (
2022-10-25 13:42:46 +02:00
" /cool/ " + subPath + " ?WOPISrc= " + encodedFrom +
2020-07-02 14:36:50 +02:00
" &ServerId= " + Util : : getProcessIdentifier ( ) +
2019-05-29 17:26:16 +02:00
" &ViewId= " + std : : to_string ( getKitViewId ( ) ) +
2022-10-25 13:42:46 +02:00
" &Tag= " + tag ) ;
2019-05-29 17:26:16 +02:00
2022-12-06 04:45:59 +01:00
# if !MOBILEAPP
if ( ! COOLWSD : : RouteToken . empty ( ) )
meta + = " &RouteToken= " + COOLWSD : : RouteToken ;
# endif
2019-06-21 18:48:57 +02:00
if ( ! encode )
return meta ;
2022-10-25 13:37:34 +02:00
return Util : : encodeURIComponent ( meta ) ;
2019-05-29 17:26:16 +02:00
}
2019-06-21 13:35:17 +02:00
bool ClientSession : : matchesClipboardKeys ( const std : : string & /*viewId*/ , const std : : string & tag )
2019-05-29 17:26:16 +02:00
{
2019-08-06 17:26:07 +02:00
if ( tag . empty ( ) )
{
LOG_ERR ( " Invalid, empty clipboard tag " ) ;
return false ;
}
2019-06-21 13:35:17 +02:00
// FIXME: check viewId for paranoia if we can.
2020-10-02 09:08:33 +02:00
return std : : any_of ( std : : begin ( _clipboardKeys ) , std : : end ( _clipboardKeys ) ,
[ & tag ] ( const std : : string & it ) { return it = = tag ; } ) ;
2019-05-29 17:26:16 +02:00
}
2019-07-04 11:50:33 +02:00
2019-06-22 18:45:36 +02:00
void ClientSession : : handleClipboardRequest ( DocumentBroker : : ClipboardRequest type ,
const std : : shared_ptr < StreamSocket > & socket ,
2019-07-04 11:50:33 +02:00
const std : : string & tag ,
2019-06-22 18:45:36 +02:00
const std : : shared_ptr < std : : string > & data )
2019-05-29 17:26:16 +02:00
{
// Move the socket into our DocBroker.
auto docBroker = getDocumentBroker ( ) ;
docBroker - > addSocketToPoll ( socket ) ;
2019-07-04 11:50:33 +02:00
if ( _state = = SessionState : : WAIT_DISCONNECT )
{
LOG_TRC ( " Clipboard request " < < tag < < " for disconnecting session " ) ;
if ( docBroker - > lookupSendClipboardTag ( socket , tag , false ) )
return ; // the getclipboard already completed.
if ( type = = DocumentBroker : : CLIP_REQUEST_SET )
{
2023-10-22 11:57:32 +02:00
# if !MOBILEAPP
HttpHelper : : sendErrorAndShutdown ( http : : StatusCode : : BadRequest , socket ) ;
# endif
2019-07-04 11:50:33 +02:00
}
else // will be handled during shutdown
{
LOG_TRC ( " Clipboard request " < < tag < < " queued for shutdown " ) ;
_clipSockets . push_back ( socket ) ;
}
}
2019-06-26 02:43:07 +02:00
std : : string specific ;
if ( type = = DocumentBroker : : CLIP_REQUEST_GET_RICH_HTML_ONLY )
specific = " text/html " ;
cool#8465 clipboard: improve handling of plain text copy, complex case
In case the selection is complex (not simple), we used to just request
HTML, and then the browser converted that to plain text, which has the
downsides already mentioned in commit
7f9de46688a64b42ba8f65cceb9fe2c6ddab89ef (cool#8465 clipboard: improve
handling of plain text copy, simple case, 2024-03-08).
Steps to support this:
1) Clipboard.js now asks for the text/html,text/plain;charset=utf-8 MIME
types.
2) wsd: ClientRequestDispatcher::handleClipboardRequest() now maps this
to DocumentBroker::CLIP_REQUEST_GET_HTML_PLAIN_ONLY
3) ClientSession::handleClipboardRequest() maps this to the HTML+plain
text MIME type list.
4) kit: ChildSession::getClipboard() is now improved to take a list of
MIME types, not just 1 or everything.
5) kit: ChildSession::getClipboard() now emits JSON in case not all, but
multiple MIME types are requested.
6) wsd: ClientSession::postProcessCopyPayload() now knows how to
postprocess clipboardcontent messages, which may or may not be JSON
(it's JSON if more formats are requested explicitly, leaving the 1
format or all format cases unchanged)
7) Control.DownloadProgress.js now handles the case when we get JSON and
sets the core-provided plain text next to the HTML.
Leave the handling of non-JSON case in, because this means we can
copy from an old COOL server to a new one.
Note that this approach has the benefit that once the clipboard marker
is inserted, the length of the text/html format would change, which
means we can't parse the clipboard data till the marker is removed.
Emitting JSON for html+text means adding the marker keeps the ability to
parse the HTML and the plain text part of the clipboard in JS.
Signed-off-by: Miklos Vajna <vmiklos@collabora.com>
Change-Id: I67a1f669e8a638d34cc25a2f288a7b30884b9892
2024-03-19 12:12:16 +01:00
else if ( type = = DocumentBroker : : CLIP_REQUEST_GET_HTML_PLAIN_ONLY )
{
specific = " text/html,text/plain;charset=utf-8 " ;
}
2019-06-26 02:43:07 +02:00
if ( type ! = DocumentBroker : : CLIP_REQUEST_SET )
2019-06-22 18:45:36 +02:00
{
2021-07-25 14:49:07 +02:00
if ( _wopiFileInfo & & _wopiFileInfo - > getDisableCopy ( ) )
{
// Unsupported clipboard request.
LOG_ERR ( " Unsupported Clipboard Request from socket # " < < socket - > getFD ( )
< < " . Terminating connection. " ) ;
std : : ostringstream oss ;
oss < < " HTTP/1.1 403 Forbidden \r \n "
< < " Date: " < < Util : : getHttpTimeNow ( ) < < " \r \n "
2024-02-20 18:23:58 +01:00
< < " User-Agent: " < < http : : getAgentString ( ) < < " \r \n "
2021-07-25 14:49:07 +02:00
< < " Content-Length: 0 \r \n "
2022-09-08 13:28:28 +02:00
< < " Connection: close \r \n "
2021-07-25 14:49:07 +02:00
< < " \r \n " ;
socket - > send ( oss . str ( ) ) ;
socket - > closeConnection ( ) ; // Shutdown socket.
socket - > ignoreInput ( ) ;
return ;
}
2019-06-26 02:43:07 +02:00
LOG_TRC ( " Session [ " < < getId ( ) < < " ] sending getclipboard " + specific ) ;
2022-11-27 04:23:17 +01:00
docBroker - > forwardToChild ( client_from_this ( ) , " getclipboard " + specific ) ;
2019-06-22 18:45:36 +02:00
_clipSockets . push_back ( socket ) ;
}
else // REQUEST_SET
{
// FIXME: manage memory more efficiently.
LOG_TRC ( " Session [ " < < getId ( ) < < " ] sending setclipboard " ) ;
2019-06-24 11:31:44 +02:00
if ( data . get ( ) )
{
2021-10-22 09:20:19 +02:00
preProcessSetClipboardPayload ( * data ) ;
2022-11-27 04:23:17 +01:00
docBroker - > forwardToChild ( client_from_this ( ) , " setclipboard \n " + * data , true ) ;
2019-06-24 11:31:44 +02:00
// FIXME: work harder for error detection ?
std : : ostringstream oss ;
oss < < " HTTP/1.1 200 OK \r \n "
2019-08-23 19:57:24 +02:00
< < " Date: " < < Util : : getHttpTimeNow ( ) < < " \r \n "
2024-02-20 18:23:58 +01:00
< < " User-Agent: " < < http : : getAgentString ( ) < < " \r \n "
2019-06-24 11:31:44 +02:00
< < " Content-Length: 0 \r \n "
2022-09-08 13:28:28 +02:00
< < " Connection: close \r \n "
2019-06-24 11:31:44 +02:00
< < " \r \n " ;
socket - > send ( oss . str ( ) ) ;
socket - > shutdown ( ) ;
}
else
{
2023-10-22 11:57:32 +02:00
# if !MOBILEAPP
HttpHelper : : sendErrorAndShutdown ( http : : StatusCode : : BadRequest , socket ) ;
# endif
2019-06-24 11:31:44 +02:00
}
2019-06-22 18:45:36 +02:00
}
2017-01-11 22:45:14 +01:00
}
2016-12-14 01:20:05 +01:00
2023-12-05 10:37:23 +01:00
void ClientSession : : onTileProcessed ( TileWireId wireId )
2023-08-03 10:30:26 +02:00
{
auto iter = std : : find_if ( _tilesOnFly . begin ( ) , _tilesOnFly . end ( ) ,
2023-12-05 10:37:23 +01:00
[ wireId ] ( const std : : pair < TileWireId , std : : chrono : : steady_clock : : time_point > & curTile )
2023-08-03 10:30:26 +02:00
{
2023-12-05 10:37:23 +01:00
return curTile . first = = wireId ;
2023-08-03 10:30:26 +02:00
} ) ;
if ( iter ! = _tilesOnFly . end ( ) )
_tilesOnFly . erase ( iter ) ;
else
2023-12-05 10:37:23 +01:00
LOG_INF ( " Tileprocessed message with an unknown wire-id ' " < < wireId < < " ' from session " < < getId ( ) ) ;
2023-08-03 10:30:26 +02:00
}
2016-05-17 01:05:22 +02:00
bool ClientSession : : _handleInput ( const char * buffer , int length )
{
2022-06-04 15:16:04 +02:00
LOG_TRC ( " handling incoming [ " < < getAbbreviatedMessage ( buffer , length ) < < ' ] ' ) ;
2016-05-17 01:05:22 +02:00
const std : : string firstLine = getFirstLine ( buffer , length ) ;
2022-03-30 03:37:57 +02:00
const StringVector tokens = StringVector : : tokenize ( firstLine . data ( ) , firstLine . size ( ) ) ;
2016-05-17 01:05:22 +02:00
2018-02-07 10:17:59 +01:00
std : : shared_ptr < DocumentBroker > docBroker = getDocumentBroker ( ) ;
2019-06-16 20:05:33 +02:00
if ( ! docBroker | | docBroker - > isMarkedToDestroy ( ) )
2016-10-16 19:20:49 +02:00
{
2019-06-16 20:05:33 +02:00
LOG_ERR ( " No DocBroker found, or DocBroker marked to be destroyed. Terminating session " < < getName ( ) ) ;
2016-10-16 19:20:49 +02:00
return false ;
}
2019-05-17 15:26:07 +02:00
if ( tokens . size ( ) < 1 )
{
2020-03-30 11:59:20 +02:00
sendTextFrameAndLogError ( " error: cmd=empty kind=unknown " ) ;
2019-05-17 15:26:07 +02:00
return false ;
}
2020-10-15 15:44:02 +02:00
if ( tokens . equals ( 0 , " DEBUG " ) )
{
2021-01-25 12:35:57 +01:00
LOG_DBG ( " From client: " < < std : : string ( buffer , length ) . substr ( strlen ( " DEBUG " ) + 1 ) ) ;
2020-10-15 15:44:02 +02:00
return false ;
2021-01-26 13:39:32 +01:00
}
else if ( tokens . equals ( 0 , " ERROR " ) )
{
LOG_ERR ( " From client: " < < std : : string ( buffer , length ) . substr ( strlen ( " ERROR " ) + 1 ) ) ;
return false ;
2020-10-15 15:44:02 +02:00
}
2021-05-04 09:53:33 +02:00
else if ( tokens . equals ( 0 , " TRACEEVENT " ) )
{
2021-11-18 13:08:14 +01:00
if ( COOLWSD : : EnableTraceEventLogging )
2021-05-04 09:53:33 +02:00
{
2021-06-22 14:50:00 +02:00
if ( _performanceCounterEpoch = = 0 )
{
static bool warnedOnce = false ;
if ( ! warnedOnce )
{
2021-11-03 15:44:08 +01:00
LOG_WRN ( " For some reason the _performanceCounterEpoch is still zero, ignoring TRACEEVENT from cool as the timestamp would be garbage " ) ;
2021-06-22 14:50:00 +02:00
warnedOnce = true ;
}
return false ;
} else if ( _performanceCounterEpoch < 1620000000000000ull | | _performanceCounterEpoch > 2000000000000000ull )
{
static bool warnedOnce = false ;
if ( ! warnedOnce )
{
2021-11-03 15:44:08 +01:00
LOG_WRN ( " For some reason the _performanceCounterEpoch is bogus, ignoring TRACEEVENT from cool as the timestamp would be garbage " ) ;
2021-06-22 14:50:00 +02:00
warnedOnce = true ;
}
return false ;
}
2021-05-06 14:44:42 +02:00
if ( tokens . size ( ) > = 4 )
2021-05-04 09:53:33 +02:00
{
2021-06-22 14:53:25 +02:00
// The intent is that when doing Trace Event generation, the web browser client and
2021-05-11 15:04:37 +02:00
// the server run on the same machine, so there is no clock skew problem.
2021-05-06 14:44:42 +02:00
std : : string name ;
std : : string ph ;
uint64_t ts ;
if ( getTokenString ( tokens [ 1 ] , " name " , name ) & &
getTokenString ( tokens [ 2 ] , " ph " , ph ) & &
getTokenUInt64 ( tokens [ 3 ] , " ts " , ts ) )
2021-05-04 09:53:33 +02:00
{
2021-05-11 15:04:37 +02:00
std : : string args ;
if ( tokens . size ( ) > = 5 & & getTokenString ( tokens , " args " , args ) )
args = " , \" args \" : " + args ;
2021-12-11 22:00:23 +01:00
uint64_t id , tid ;
2021-05-06 14:44:42 +02:00
uint64_t dur ;
if ( ph = = " i " )
{
2023-12-28 23:41:55 +01:00
COOLWSD : : writeTraceEventRecording ( " { \" name \" : "
2021-06-18 13:53:51 +02:00
+ name
2023-12-28 23:41:55 +01:00
+ " , \" ph \" : \" i \" "
2021-06-18 13:53:51 +02:00
+ args
+ " , \" ts \" : "
+ std : : to_string ( ts + _performanceCounterEpoch )
+ " , \" pid \" : "
2021-11-03 15:39:37 +01:00
+ std : : to_string ( getpid ( ) + SYNTHETIC_COOL_PID_OFFSET )
2021-06-22 11:16:51 +02:00
+ " , \" tid \" :1}, \n " ) ;
2021-05-06 14:44:42 +02:00
}
2023-05-15 20:35:45 +02:00
// Should the first getTokenUInt64()'s return value really
// be ignored?
2021-06-23 14:03:55 +02:00
else if ( ( ph = = " S " | | ph = = " F " ) & &
2023-05-15 20:35:45 +02:00
( static_cast < void > ( getTokenUInt64 ( tokens [ 4 ] , " id " , id ) ) ,
getTokenUInt64 ( tokens [ 5 ] , " tid " , tid ) ) )
2021-05-06 14:44:42 +02:00
{
2023-12-28 23:41:55 +01:00
COOLWSD : : writeTraceEventRecording ( " { \" name \" : "
2021-06-18 13:53:51 +02:00
+ name
2023-12-28 23:41:55 +01:00
+ " , \" ph \" : \" "
2021-06-18 13:53:51 +02:00
+ ph
+ " \" "
+ args
+ " , \" ts \" : "
+ std : : to_string ( ts + _performanceCounterEpoch )
+ " , \" pid \" : "
2021-11-03 15:39:37 +01:00
+ std : : to_string ( getpid ( ) + SYNTHETIC_COOL_PID_OFFSET )
2021-12-11 22:00:23 +01:00
+ " , \" tid \" : "
+ std : : to_string ( tid )
+ " , \" id \" : "
2021-06-18 13:53:51 +02:00
+ std : : to_string ( id )
+ " }, \n " ) ;
2021-05-06 14:44:42 +02:00
}
else if ( ph = = " X " & &
getTokenUInt64 ( tokens [ 4 ] , " dur " , dur ) )
{
2023-12-28 23:41:55 +01:00
COOLWSD : : writeTraceEventRecording ( " { \" name \" : "
2021-06-18 13:53:51 +02:00
+ name
2023-12-28 23:41:55 +01:00
+ " , \" ph \" : \" X \" "
2021-06-18 13:53:51 +02:00
+ args
+ " , \" ts \" : "
+ std : : to_string ( ts + _performanceCounterEpoch )
+ " , \" pid \" : "
2021-11-03 15:39:37 +01:00
+ std : : to_string ( getpid ( ) + SYNTHETIC_COOL_PID_OFFSET )
2021-06-22 11:16:51 +02:00
+ " , \" tid \" :1 "
" , \" dur \" : "
2021-06-18 13:53:51 +02:00
+ std : : to_string ( dur )
+ " }, \n " ) ;
2021-05-06 14:44:42 +02:00
}
else
{
LOG_WRN ( " Unrecognized TRACEEVENT message " ) ;
}
2021-05-04 09:53:33 +02:00
}
}
2021-05-06 14:44:42 +02:00
else
LOG_WRN ( " Unrecognized TRACEEVENT message " ) ;
2021-05-04 09:53:33 +02:00
}
return false ;
}
2020-10-15 15:44:02 +02:00
2021-11-18 13:08:14 +01:00
COOLWSD : : dumpIncomingTrace ( docBroker - > getJailId ( ) , getId ( ) , firstLine ) ;
2016-07-31 05:06:12 +02:00
2021-11-18 13:08:14 +01:00
if ( COOLProtocol : : tokenIndicatesUserInteraction ( tokens [ 0 ] ) )
2016-05-17 01:05:22 +02:00
{
// Keep track of timestamps of incoming client messages that indicate user activity.
updateLastActivityTime ( ) ;
2016-12-05 21:11:07 +01:00
docBroker - > updateLastActivityTime ( ) ;
2021-08-15 10:29:17 +02:00
2022-12-10 13:03:56 +01:00
if ( isEditable ( ) & & isViewLoaded ( ) )
2021-08-15 10:29:17 +02:00
{
2022-04-02 23:19:50 +02:00
assert ( ! inWaitDisconnected ( ) & & " A writable view can't be waiting disconnection. " ) ;
2021-08-15 10:29:17 +02:00
docBroker - > updateEditingSessionId ( getId ( ) ) ;
}
2016-05-17 01:05:22 +02:00
}
2023-02-04 16:23:20 +01:00
2023-07-31 16:28:27 +02:00
if ( tokens . equals ( 0 , " urp " ) )
{
// This can't be pushed down into the long list of tokens that are
// forwarded to the child later as we need it to be able to run before
// documents are loaded
LOG_TRC ( " UNO remote protocol message (from client): " < < firstLine ) ;
return forwardToChild ( std : : string ( buffer , length ) , docBroker ) ;
}
2021-11-17 11:16:38 +01:00
if ( tokens . equals ( 0 , " coolclient " ) )
2016-05-17 01:05:22 +02:00
{
2019-05-17 15:26:07 +02:00
if ( tokens . size ( ) < 2 )
2017-09-27 10:15:30 +02:00
{
2021-11-17 11:16:38 +01:00
sendTextFrameAndLogError ( " error: cmd=coolclient kind=badprotocolversion " ) ;
2017-09-27 10:15:30 +02:00
return false ;
}
2018-02-07 10:17:59 +01:00
const std : : tuple < int , int , std : : string > versionTuple = ParseVersion ( tokens [ 1 ] ) ;
2016-05-17 01:05:22 +02:00
if ( std : : get < 0 > ( versionTuple ) ! = ProtocolMajorVersionNumber | |
std : : get < 1 > ( versionTuple ) ! = ProtocolMinorVersionNumber )
{
2021-11-17 11:16:38 +01:00
sendTextFrameAndLogError ( " error: cmd=coolclient kind=badprotocolversion " ) ;
2016-05-17 01:05:22 +02:00
return false ;
}
2021-05-04 09:53:33 +02:00
_performanceCounterEpoch = 0 ;
if ( tokens . size ( ) > = 4 )
{
2023-02-04 16:23:20 +01:00
const std : : string timestamp = tokens [ 2 ] ;
2021-06-08 11:31:05 +02:00
const char * str = timestamp . c_str ( ) ;
2021-05-04 09:53:33 +02:00
char * endptr = nullptr ;
uint64_t ts = strtoull ( str , & endptr , 10 ) ;
if ( * endptr = = ' \0 ' )
{
2023-02-04 16:23:20 +01:00
const std : : string perfcounter = tokens [ 3 ] ;
2021-05-10 09:24:44 +02:00
str = perfcounter . data ( ) ;
2021-05-04 09:53:33 +02:00
endptr = nullptr ;
double counter = strtod ( str , & endptr ) ;
2021-06-03 16:48:17 +02:00
if ( * endptr = = ' \0 ' & & counter > 0 & &
2023-08-30 08:34:28 +02:00
( counter < ( double ) ( uint64_t ) ( std : : numeric_limits < uint64_t > : : max ( ) / 1000 ) ) )
2021-05-04 09:53:33 +02:00
{
// Now we know how to translate from the client's performance.now() values to
// microseconds since the epoch.
_performanceCounterEpoch = ts * 1000 - ( uint64_t ) ( counter * 1000 ) ;
LOG_INF ( " Client timestamps: Date.now(): " < < ts < <
" , performance.now(): " < < counter
< < " => " < < _performanceCounterEpoch ) ;
}
}
}
2021-11-15 17:00:44 +01:00
// Send COOL version information
2022-03-23 21:16:56 +01:00
sendTextFrame ( " coolserver " + Util : : getVersionJSON ( EnableExperimental ) ) ;
2016-06-20 20:58:00 +02:00
// Send LOKit version information
2021-11-18 13:08:14 +01:00
sendTextFrame ( " lokitversion " + COOLWSD : : LOKitVersion ) ;
2020-04-10 19:02:39 +02:00
2021-05-10 14:54:25 +02:00
// If Trace Event generation and logging is enabled (whether it can be turned on), tell it
2021-11-03 15:31:18 +01:00
// to cool
2021-11-18 13:08:14 +01:00
if ( COOLWSD : : EnableTraceEventLogging )
2021-05-10 14:54:25 +02:00
sendTextFrame ( " enabletraceeventlogging yes " ) ;
2024-03-17 23:11:02 +01:00
if ( ! Util : : isMobileApp ( ) )
{
2020-04-10 19:02:39 +02:00
// If it is not mobile, it must be Linux (for now).
2024-01-30 04:03:58 +01:00
std : : string osVersionInfo ( COOLWSD : : getConfigValue < std : : string > ( " per_view.custom_os_info " , " " ) ) ;
if ( osVersionInfo . empty ( ) )
osVersionInfo = Util : : getLinuxVersion ( ) ;
sendTextFrame ( std : : string ( " osinfo " ) + osVersionInfo ) ;
2024-03-17 23:11:02 +01:00
}
2020-04-10 19:02:39 +02:00
2019-06-21 13:35:17 +02:00
// Send clipboard key
2019-06-22 22:23:12 +02:00
rotateClipboardKey ( true ) ;
2016-06-20 20:58:00 +02:00
return true ;
2016-05-17 01:05:22 +02:00
}
2019-07-02 02:25:10 +02:00
2023-03-08 10:34:07 +01:00
if ( tokens . equals ( 0 , " versionbar " ) )
2022-05-05 22:08:31 +02:00
{
# if !MOBILEAPP
2023-03-08 10:34:07 +01:00
std : : string versionBar ;
2022-05-05 22:08:31 +02:00
{
std : : lock_guard < std : : mutex > lock ( COOLWSD : : FetchUpdateMutex ) ;
2023-03-08 10:34:07 +01:00
versionBar = COOLWSD : : LatestVersion ;
2022-05-05 22:08:31 +02:00
}
2023-03-08 10:34:07 +01:00
if ( ! versionBar . empty ( ) )
sendTextFrame ( " versionbar: " + versionBar ) ;
2022-05-05 22:08:31 +02:00
# endif
}
else if ( tokens . equals ( 0 , " jserror " ) | | tokens . equals ( 0 , " jsexception " ) )
2019-07-02 02:25:10 +02:00
{
LOG_ERR ( std : : string ( buffer , length ) ) ;
return true ;
}
2020-03-09 09:05:30 +01:00
else if ( tokens . equals ( 0 , " load " ) )
2016-05-17 01:05:22 +02:00
{
2024-03-11 13:20:03 +01:00
if ( ! getDocURL ( ) . empty ( ) )
2016-05-17 01:05:22 +02:00
{
2020-03-30 11:59:20 +02:00
sendTextFrameAndLogError ( " error: cmd=load kind=docalreadyloaded " ) ;
2016-05-17 01:05:22 +02:00
return false ;
}
2016-10-16 19:20:49 +02:00
return loadDocument ( buffer , length , tokens , docBroker ) ;
2016-05-17 01:05:22 +02:00
}
2020-09-10 16:04:36 +02:00
else if ( tokens . equals ( 0 , " loadwithpassword " ) )
{
std : : string docPassword ;
if ( tokens . size ( ) > 1 & & getTokenString ( tokens [ 1 ] , " password " , docPassword ) )
{
if ( ! docPassword . empty ( ) )
{
setHaveDocPassword ( true ) ;
setDocPassword ( docPassword ) ;
}
}
return loadDocument ( buffer , length , tokens , docBroker ) ;
}
2020-06-03 05:17:04 +02:00
else if ( getDocURL ( ) . empty ( ) )
2016-05-17 01:05:22 +02:00
{
2020-03-30 11:59:20 +02:00
sendTextFrameAndLogError ( " error: cmd= " + tokens [ 0 ] + " kind=nodocloaded " ) ;
2016-05-17 01:05:22 +02:00
return false ;
}
2020-03-09 09:05:30 +01:00
else if ( tokens . equals ( 0 , " commandvalues " ) )
2016-05-17 01:05:22 +02:00
{
2016-10-16 19:20:49 +02:00
return getCommandValues ( buffer , length , tokens , docBroker ) ;
2016-05-17 01:05:22 +02:00
}
2020-03-09 09:05:30 +01:00
else if ( tokens . equals ( 0 , " closedocument " ) )
2016-11-08 14:37:28 +01:00
{
2016-12-13 10:16:34 +01:00
// If this session is the owner of the file & 'EnableOwnerTermination' feature
// is turned on by WOPI, let it close all sessions
2019-10-16 15:12:02 +02:00
if ( isDocumentOwner ( ) & & _wopiFileInfo & & _wopiFileInfo - > getEnableOwnerTermination ( ) )
2016-11-08 14:37:28 +01:00
{
2016-12-22 00:27:37 +01:00
LOG_DBG ( " Session [ " < < getId ( ) < < " ] requested owner termination " ) ;
2016-11-08 14:37:28 +01:00
docBroker - > closeDocument ( " ownertermination " ) ;
}
2017-06-01 14:56:54 +02:00
else if ( docBroker - > isDocumentChangedInStorage ( ) )
{
LOG_DBG ( " Document marked as changed in storage and user [ "
< < getUserId ( ) < < " , " < < getUserName ( )
< < " ] wants to refresh the document for all. " ) ;
2018-02-02 08:38:25 +01:00
docBroker - > stop ( " documentconflict " + getUserName ( ) ) ;
2017-06-01 14:56:54 +02:00
}
2016-11-08 14:37:28 +01:00
return true ;
}
2020-03-09 09:05:30 +01:00
else if ( tokens . equals ( 0 , " versionrestore " ) )
2019-05-23 18:55:01 +02:00
{
2020-03-09 09:05:30 +01:00
if ( tokens . size ( ) > 1 & & tokens . equals ( 1 , " prerestore " ) )
2019-05-23 18:55:01 +02:00
{
2018-02-02 07:05:29 +01:00
// green signal to WOPI host to restore the version *after* saving
// any unsaved changes, if any, to the storage
docBroker - > closeDocument ( " versionrestore: prerestore_ack " ) ;
}
}
2020-03-09 09:05:30 +01:00
else if ( tokens . equals ( 0 , " partpagerectangles " ) )
2016-05-17 01:05:22 +02:00
{
2017-02-04 20:41:33 +01:00
// We don't support partpagerectangles any more, will be removed in the
// next version
sendTextFrame ( " partpagerectangles: " ) ;
return true ;
2016-05-17 01:05:22 +02:00
}
2020-03-09 09:05:30 +01:00
else if ( tokens . equals ( 0 , " ping " ) )
2016-09-27 20:22:09 +02:00
{
2016-10-16 19:20:49 +02:00
std : : string count = std : : to_string ( docBroker - > getRenderedTileCount ( ) ) ;
2016-10-11 14:39:56 +02:00
sendTextFrame ( " pong rendercount= " + count ) ;
2016-09-27 20:22:09 +02:00
return true ;
}
2020-03-09 09:05:30 +01:00
else if ( tokens . equals ( 0 , " renderfont " ) )
2016-05-17 01:05:22 +02:00
{
2016-10-16 19:20:49 +02:00
return sendFontRendering ( buffer , length , tokens , docBroker ) ;
2016-05-17 01:05:22 +02:00
}
2020-03-09 09:05:30 +01:00
else if ( tokens . equals ( 0 , " status " ) | | tokens . equals ( 0 , " statusupdate " ) )
2016-05-17 01:05:22 +02:00
{
2020-11-15 18:03:45 +01:00
assert ( firstLine . size ( ) = = static_cast < std : : size_t > ( length ) ) ;
2016-10-16 19:43:44 +02:00
return forwardToChild ( firstLine , docBroker ) ;
2016-05-17 01:05:22 +02:00
}
2020-03-09 09:05:30 +01:00
else if ( tokens . equals ( 0 , " tile " ) )
2016-05-17 01:05:22 +02:00
{
2023-09-08 13:01:37 +02:00
if ( ! ( UnitWSD : : isUnitTesting ( ) ? true : getCanonicalViewId ( ) ! = 0 & & getCanonicalViewId ( ) > = 1000 ) )
{
LOG_WRN ( " Got tile request for session [ " < < getId ( ) < < " ] on document [ " < < docBroker - > getDocKey ( )
< < " ] with invalid view ID [ " < < getCanonicalViewId ( ) < < " ]. " ) ;
}
2016-10-16 19:20:49 +02:00
return sendTile ( buffer , length , tokens , docBroker ) ;
2016-05-17 01:05:22 +02:00
}
2020-03-09 09:05:30 +01:00
else if ( tokens . equals ( 0 , " tilecombine " ) )
2016-05-17 01:05:22 +02:00
{
2023-09-08 13:01:37 +02:00
if ( ! ( UnitWSD : : isUnitTesting ( ) ? true : getCanonicalViewId ( ) ! = 0 & & getCanonicalViewId ( ) > = 1000 ) )
{
LOG_WRN ( " Got tilecombine request for session [ " < < getId ( ) < < " ] on document [ " < < docBroker - > getDocKey ( )
< < " ] with invalid view ID [ " < < getCanonicalViewId ( ) < < " ]. " ) ;
}
2016-10-16 19:20:49 +02:00
return sendCombinedTiles ( buffer , length , tokens , docBroker ) ;
2016-05-17 01:05:22 +02:00
}
2020-03-09 09:05:30 +01:00
else if ( tokens . equals ( 0 , " save " ) )
2017-05-15 07:59:04 +02:00
{
2022-12-10 15:20:58 +01:00
// If we can't write to Storage, there is no point in saving.
if ( ! isWritable ( ) )
wsd: block save of read-only documents
WSD is responsible for checking permissions,
as we do with DisableCopy and DisablePrint,
but until now we allowed saving even on
read-only documents.
The reason, it seems, was that when we failed
to save a document due to disk space, we
set documents as read-only. So presumably
we still had to allow saving, so users
preserve their latest changes when the disk
has some free space. Meanwhile, we didn't
let users make further changes. At least
this seems to be a reasonable explanation.
Unfortunately this meant that we allowed
saving when the user had no permission,
or the document was loaded as read-only.
Now we no longer mark documents that fail
to save due to disk-space limitation as
read-only, and instead expect the client
to notify the user and (possibly) block
further edits. And read-only documents,
or users without write permission, are
no longer allowed to get saved.
This change makes sure that the ctrl+s
shortcut respects the read-only flag,
while we allow clients to disable
the default handler and decide what to
do (although now they cannot force saving
when the user has no permission, something
they could do previously).
Change-Id: I16c3b75fd3e54435d750948a25afd6f71c9f963b
Reviewed-on: https://gerrit.libreoffice.org/77594
Reviewed-by: Ashod Nakashian <ashnakash@gmail.com>
Tested-by: Ashod Nakashian <ashnakash@gmail.com>
2019-08-14 02:03:40 +02:00
{
2022-12-10 15:20:58 +01:00
LOG_WRN ( " Session [ " < < getId ( ) < < " ] on document [ " < < docBroker - > getDocKey ( )
< < " ] has no write permissions in Storage and cannot save. " ) ;
sendTextFrameAndLogError ( " error: cmd=save kind=savefailed " ) ;
wsd: block save of read-only documents
WSD is responsible for checking permissions,
as we do with DisableCopy and DisablePrint,
but until now we allowed saving even on
read-only documents.
The reason, it seems, was that when we failed
to save a document due to disk space, we
set documents as read-only. So presumably
we still had to allow saving, so users
preserve their latest changes when the disk
has some free space. Meanwhile, we didn't
let users make further changes. At least
this seems to be a reasonable explanation.
Unfortunately this meant that we allowed
saving when the user had no permission,
or the document was loaded as read-only.
Now we no longer mark documents that fail
to save due to disk-space limitation as
read-only, and instead expect the client
to notify the user and (possibly) block
further edits. And read-only documents,
or users without write permission, are
no longer allowed to get saved.
This change makes sure that the ctrl+s
shortcut respects the read-only flag,
while we allow clients to disable
the default handler and decide what to
do (although now they cannot force saving
when the user has no permission, something
they could do previously).
Change-Id: I16c3b75fd3e54435d750948a25afd6f71c9f963b
Reviewed-on: https://gerrit.libreoffice.org/77594
Reviewed-by: Ashod Nakashian <ashnakash@gmail.com>
Tested-by: Ashod Nakashian <ashnakash@gmail.com>
2019-08-14 02:03:40 +02:00
}
else
{
// Don't save unmodified docs by default.
int dontSaveIfUnmodified = 1 ;
2022-11-19 19:19:01 +01:00
int dontTerminateEdit = 1 ;
2019-06-16 20:42:11 +02:00
std : : string extendedData ;
2022-11-19 19:19:01 +01:00
// We expect at most 3 arguments.
for ( int i = 0 ; i < 3 ; + + i )
2019-11-16 17:59:39 +01:00
{
2022-11-19 19:19:01 +01:00
// +1 to skip the command token.
const StringVector attr = StringVector : : tokenize ( tokens [ i + 1 ] , ' = ' ) ;
if ( attr . size ( ) = = 2 )
{
if ( attr [ 0 ] = = " dontTerminateEdit " )
COOLProtocol : : stringToInteger ( attr [ 1 ] , dontTerminateEdit ) ;
else if ( attr [ 0 ] = = " dontSaveIfUnmodified " )
COOLProtocol : : stringToInteger ( attr [ 1 ] , dontSaveIfUnmodified ) ;
else if ( attr [ 0 ] = = " extendedData " )
{
std : : string decoded ;
Poco : : URI : : decode ( attr [ 1 ] , decoded ) ;
extendedData = decoded ;
}
}
2019-11-16 17:59:39 +01:00
}
2019-06-16 20:42:11 +02:00
2023-04-02 15:23:45 +02:00
docBroker - > manualSave ( client_from_this ( ) , dontTerminateEdit ! = 0 ,
dontSaveIfUnmodified ! = 0 , extendedData ) ;
wsd: block save of read-only documents
WSD is responsible for checking permissions,
as we do with DisableCopy and DisablePrint,
but until now we allowed saving even on
read-only documents.
The reason, it seems, was that when we failed
to save a document due to disk space, we
set documents as read-only. So presumably
we still had to allow saving, so users
preserve their latest changes when the disk
has some free space. Meanwhile, we didn't
let users make further changes. At least
this seems to be a reasonable explanation.
Unfortunately this meant that we allowed
saving when the user had no permission,
or the document was loaded as read-only.
Now we no longer mark documents that fail
to save due to disk-space limitation as
read-only, and instead expect the client
to notify the user and (possibly) block
further edits. And read-only documents,
or users without write permission, are
no longer allowed to get saved.
This change makes sure that the ctrl+s
shortcut respects the read-only flag,
while we allow clients to disable
the default handler and decide what to
do (although now they cannot force saving
when the user has no permission, something
they could do previously).
Change-Id: I16c3b75fd3e54435d750948a25afd6f71c9f963b
Reviewed-on: https://gerrit.libreoffice.org/77594
Reviewed-by: Ashod Nakashian <ashnakash@gmail.com>
Tested-by: Ashod Nakashian <ashnakash@gmail.com>
2019-08-14 02:03:40 +02:00
}
2017-05-15 07:59:04 +02:00
}
2020-03-09 09:05:30 +01:00
else if ( tokens . equals ( 0 , " savetostorage " ) )
2017-06-01 16:16:03 +02:00
{
2020-10-20 15:26:30 +02:00
// By default savetostorage implies forcing.
int force = 1 ;
2017-09-27 10:15:30 +02:00
if ( tokens . size ( ) > 1 )
2023-08-15 12:31:45 +02:00
( void ) getTokenInteger ( tokens [ 1 ] , " force " , force ) ;
2017-09-27 10:15:30 +02:00
2020-10-20 15:26:30 +02:00
// The savetostorage command is really only used to resolve save conflicts
// and it seems to always have force=1. However, we should still honor the
// contract and do as told, not as we expect the API to be used. Use force if provided.
2022-11-24 15:37:59 +01:00
docBroker - > uploadToStorage ( client_from_this ( ) , force ) ;
2017-06-01 16:16:03 +02:00
}
2020-03-09 09:05:30 +01:00
else if ( tokens . equals ( 0 , " clientvisiblearea " ) )
2018-05-30 19:35:13 +02:00
{
int x ;
int y ;
int width ;
int height ;
2020-07-08 03:40:13 +02:00
if ( ( tokens . size ( ) ! = 5 & & tokens . size ( ) ! = 7 ) | |
2018-05-30 19:35:13 +02:00
! getTokenInteger ( tokens [ 1 ] , " x " , x ) | |
! getTokenInteger ( tokens [ 2 ] , " y " , y ) | |
! getTokenInteger ( tokens [ 3 ] , " width " , width ) | |
! getTokenInteger ( tokens [ 4 ] , " height " , height ) )
{
2019-07-03 06:29:43 +02:00
// Be forgiving and log instead of disconnecting.
2020-03-30 11:59:20 +02:00
// sendTextFrameAndLogError("error: cmd=clientvisiblearea kind=syntax");
2024-01-03 11:20:15 +01:00
logSyntaxErrorDetails ( tokens , firstLine ) ;
2019-07-03 06:29:43 +02:00
return true ;
2018-05-30 19:35:13 +02:00
}
else
{
2020-07-08 03:40:13 +02:00
if ( tokens . size ( ) = = 7 )
{
int splitX , splitY ;
if ( ! getTokenInteger ( tokens [ 5 ] , " splitx " , splitX ) | |
! getTokenInteger ( tokens [ 6 ] , " splity " , splitY ) )
{
2024-01-03 11:20:15 +01:00
logSyntaxErrorDetails ( tokens , firstLine ) ;
2020-07-08 03:40:13 +02:00
return true ;
}
_splitX = splitX ;
_splitY = splitY ;
}
2024-02-23 09:22:22 +01:00
// Untrusted user input, make sure these are not negative.
if ( width < 0 )
{
width = 0 ;
}
if ( height < 0 )
{
height = 0 ;
}
2018-05-30 19:35:13 +02:00
_clientVisibleArea = Util : : Rectangle ( x , y , width , height ) ;
return forwardToChild ( std : : string ( buffer , length ) , docBroker ) ;
}
}
2020-03-09 09:05:30 +01:00
else if ( tokens . equals ( 0 , " setclientpart " ) )
2018-05-30 19:35:13 +02:00
{
2018-06-19 16:15:37 +02:00
if ( ! _isTextDocument )
2018-05-30 19:35:13 +02:00
{
2018-06-19 16:15:37 +02:00
int temp ;
if ( tokens . size ( ) ! = 2 | |
! getTokenInteger ( tokens [ 1 ] , " part " , temp ) )
{
2024-01-03 11:20:15 +01:00
logSyntaxErrorDetails ( tokens , firstLine ) ;
2018-06-19 16:15:37 +02:00
return false ;
}
else
{
_clientSelectedPart = temp ;
return forwardToChild ( std : : string ( buffer , length ) , docBroker ) ;
}
2018-05-30 19:35:13 +02:00
}
}
2020-03-09 09:05:30 +01:00
else if ( tokens . equals ( 0 , " selectclientpart " ) )
2018-09-12 01:30:55 +02:00
{
if ( ! _isTextDocument )
{
int part ;
int how ;
if ( tokens . size ( ) ! = 3 | |
! getTokenInteger ( tokens [ 1 ] , " part " , part ) | |
! getTokenInteger ( tokens [ 2 ] , " how " , how ) )
{
2020-03-30 11:59:20 +02:00
sendTextFrameAndLogError ( " error: cmd=selectclientpart kind=syntax " ) ;
2018-09-12 01:30:55 +02:00
return false ;
}
else
{
return forwardToChild ( std : : string ( buffer , length ) , docBroker ) ;
}
}
}
2020-03-09 09:05:30 +01:00
else if ( tokens . equals ( 0 , " moveselectedclientparts " ) )
2018-09-17 13:17:31 +02:00
{
2023-02-04 22:59:08 +01:00
if ( ! _isTextDocument )
2018-09-17 13:17:31 +02:00
{
int nPosition ;
if ( tokens . size ( ) ! = 2 | |
! getTokenInteger ( tokens [ 1 ] , " position " , nPosition ) )
{
2020-03-30 11:59:20 +02:00
sendTextFrameAndLogError ( " error: cmd=moveselectedclientparts kind=syntax " ) ;
2018-09-17 13:17:31 +02:00
return false ;
}
else
{
2023-09-21 02:02:34 +02:00
if ( isEditable ( ) )
docBroker - > updateLastModifyingActivityTime ( ) ;
2018-09-17 13:17:31 +02:00
return forwardToChild ( std : : string ( buffer , length ) , docBroker ) ;
}
}
}
2020-03-09 09:05:30 +01:00
else if ( tokens . equals ( 0 , " clientzoom " ) )
2018-05-30 19:35:13 +02:00
{
int tilePixelWidth , tilePixelHeight , tileTwipWidth , tileTwipHeight ;
if ( tokens . size ( ) ! = 5 | |
! getTokenInteger ( tokens [ 1 ] , " tilepixelwidth " , tilePixelWidth ) | |
! getTokenInteger ( tokens [ 2 ] , " tilepixelheight " , tilePixelHeight ) | |
! getTokenInteger ( tokens [ 3 ] , " tiletwipwidth " , tileTwipWidth ) | |
! getTokenInteger ( tokens [ 4 ] , " tiletwipheight " , tileTwipHeight ) )
{
2019-07-03 06:29:43 +02:00
// Be forgiving and log instead of disconnecting.
2020-03-30 11:59:20 +02:00
// sendTextFrameAndLogError("error: cmd=clientzoom kind=syntax");
2024-01-03 11:20:15 +01:00
logSyntaxErrorDetails ( tokens , firstLine ) ;
2019-07-03 06:29:43 +02:00
return true ;
2018-05-30 19:35:13 +02:00
}
else
{
_tileWidthPixel = tilePixelWidth ;
_tileHeightPixel = tilePixelHeight ;
_tileWidthTwips = tileTwipWidth ;
_tileHeightTwips = tileTwipHeight ;
return forwardToChild ( std : : string ( buffer , length ) , docBroker ) ;
}
}
2020-03-09 09:05:30 +01:00
else if ( tokens . equals ( 0 , " tileprocessed " ) )
2018-06-07 13:13:36 +02:00
{
2023-12-05 10:37:23 +01:00
std : : string wids ;
2018-06-13 15:04:09 +02:00
if ( tokens . size ( ) ! = 2 | |
2023-12-05 10:37:23 +01:00
! getTokenString ( tokens [ 1 ] , " wids " , wids ) )
2018-07-10 14:10:28 +02:00
{
2019-07-03 06:29:43 +02:00
// Be forgiving and log instead of disconnecting.
2020-03-30 11:59:20 +02:00
// sendTextFrameAndLogError("error: cmd=tileprocessed kind=syntax");
2024-01-03 11:20:15 +01:00
logSyntaxErrorDetails ( tokens , firstLine ) ;
2019-07-03 06:29:43 +02:00
return true ;
2018-06-13 15:04:09 +02:00
}
2018-08-23 12:46:49 +02:00
2023-08-03 11:59:15 +02:00
// call onTileProcessed on each tileID of tileid1, tileid2, ...
auto lambda = [ this ] ( size_t /*nIndex*/ , const std : : string_view token ) {
2023-12-05 10:37:23 +01:00
std : : string copy ( token ) ;
TileWireId wireId = 0 ; bool res ;
std : : tie ( wireId , res ) = Util : : i32FromString ( copy ) ;
if ( ! res )
LOG_WRN ( " Invalid syntax for tileprocessed wireid ' " < < token < < " ' " ) ;
onTileProcessed ( wireId ) ;
2023-08-03 11:59:15 +02:00
return false ;
} ;
2023-12-05 10:37:23 +01:00
StringVector : : tokenize_foreach ( lambda , wids . data ( ) , wids . size ( ) , ' , ' ) ;
2018-06-13 15:04:09 +02:00
2020-03-06 18:43:46 +01:00
docBroker - > sendRequestedTiles ( client_from_this ( ) ) ;
2018-06-07 13:13:36 +02:00
return true ;
}
2022-11-24 22:23:29 +01:00
else if ( tokens . equals ( 0 , " removesession " ) )
{
2023-04-06 10:43:15 +02:00
if ( tokens . size ( ) > 1 & & ( isDocumentOwner ( ) | | ! isReadOnly ( ) ) )
2019-05-23 13:11:58 +02:00
{
std : : string sessionId = Util : : encodeId ( std : : stoi ( tokens [ 1 ] ) , 4 ) ;
docBroker - > broadcastMessage ( firstLine ) ;
2022-11-24 22:23:29 +01:00
docBroker - > removeSession ( client_from_this ( ) ) ;
2019-05-23 13:11:58 +02:00
}
else
LOG_WRN ( " Readonly session ' " < < getId ( ) < < " ' trying to kill another view " ) ;
2018-12-04 23:28:55 +01:00
}
2020-03-09 09:05:30 +01:00
else if ( tokens . equals ( 0 , " renamefile " ) )
2019-05-23 18:55:01 +02:00
{
2019-04-30 16:21:44 +02:00
std : : string encodedWopiFilename ;
2019-05-23 18:55:01 +02:00
if ( tokens . size ( ) < 2 | | ! getTokenString ( tokens [ 1 ] , " filename " , encodedWopiFilename ) )
2019-04-30 16:21:44 +02:00
{
LOG_ERR ( " Bad syntax for: " < < firstLine ) ;
2020-03-30 11:59:20 +02:00
sendTextFrameAndLogError ( " error: cmd=renamefile kind=syntax " ) ;
2019-04-30 16:21:44 +02:00
return false ;
}
2021-05-25 04:09:06 +02:00
2019-04-30 16:21:44 +02:00
std : : string wopiFilename ;
Poco : : URI : : decode ( encodedWopiFilename , wopiFilename ) ;
2021-05-25 04:09:06 +02:00
const std : : string error =
docBroker - > handleRenameFileCommand ( getId ( ) , std : : move ( wopiFilename ) ) ;
if ( ! error . empty ( ) )
{
sendTextFrameAndLogError ( error ) ;
return false ;
}
2019-04-30 16:21:44 +02:00
return true ;
}
2023-04-06 10:43:15 +02:00
else if ( tokens . equals ( 0 , " dialogevent " ) )
{
if ( tokens . size ( ) > 2 )
{
std : : string jsonString = tokens . cat ( " " , 2 ) ;
try
{
Poco : : JSON : : Parser parser ;
const Poco : : Dynamic : : Var result = parser . parse ( jsonString ) ;
const auto & object = result . extract < Poco : : JSON : : Object : : Ptr > ( ) ;
const std : : string id = object - > has ( " id " ) ? object - > get ( " id " ) . toString ( ) : " " ;
if ( id = = " changepass " & & _wopiFileInfo & & ! isDocumentOwner ( ) )
{
sendTextFrameAndLogError ( " error: cmd=dialogevent kind=cantchangepass " ) ;
return false ;
}
}
catch ( const std : : exception & exception )
{
// Child will handle this case
}
}
return forwardToChild ( firstLine , docBroker ) ;
}
else if ( tokens . equals ( 0 , " formfieldevent " ) | |
2022-05-31 13:35:20 +02:00
tokens . equals ( 0 , " sallogoverride " ) | |
tokens . equals ( 0 , " contentcontrolevent " ) )
2019-10-14 18:02:28 +02:00
{
return forwardToChild ( firstLine , docBroker ) ;
}
2021-05-28 17:14:18 +02:00
else if ( tokens . equals ( 0 , " loggingleveloverride " ) )
{
if ( tokens . size ( ) > 0 )
{
// Note that these LOG_INF() messages won't necessarily show up if the current logging
// level is higher, of course.
if ( tokens . equals ( 1 , " default " ) )
{
LOG_INF ( " Thread-local logging level being set to default [ "
< < Log : : getLevel ( )
< < " ] " ) ;
2024-04-23 16:26:34 +02:00
Log : : setThreadLocalLogLevel ( Log : : getLevelName ( ) ) ;
2021-05-28 17:14:18 +02:00
}
else
{
try
{
2021-11-18 13:08:14 +01:00
auto leastVerboseAllowed = Poco : : Logger : : parseLevel ( COOLWSD : : LeastVerboseLogLevelSettableFromClient ) ;
auto mostVerboseAllowed = Poco : : Logger : : parseLevel ( COOLWSD : : MostVerboseLogLevelSettableFromClient ) ;
2021-05-28 17:14:18 +02:00
2021-05-31 13:00:21 +02:00
if ( tokens . equals ( 1 , " verbose " ) )
2021-05-28 17:14:18 +02:00
{
2021-05-31 13:00:21 +02:00
LOG_INF ( " Client sets thread-local logging level to the most verbose allowed [ "
2021-11-18 13:08:14 +01:00
< < COOLWSD : : MostVerboseLogLevelSettableFromClient
2021-05-28 17:14:18 +02:00
< < " ] " ) ;
2021-11-18 13:08:14 +01:00
Log : : setThreadLocalLogLevel ( COOLWSD : : MostVerboseLogLevelSettableFromClient ) ;
2021-05-28 17:14:18 +02:00
LOG_INF ( " Thread-local logging level was set to [ "
2021-11-18 13:08:14 +01:00
< < COOLWSD : : MostVerboseLogLevelSettableFromClient
2021-05-28 17:14:18 +02:00
< < " ] " ) ;
}
2021-05-31 13:00:21 +02:00
else if ( tokens . equals ( 1 , " terse " ) )
2021-05-28 17:14:18 +02:00
{
2021-05-31 13:00:21 +02:00
LOG_INF ( " Client sets thread-local logging level to the least verbose allowed [ "
2021-11-18 13:08:14 +01:00
< < COOLWSD : : LeastVerboseLogLevelSettableFromClient
2021-05-28 17:14:18 +02:00
< < " ] " ) ;
2021-11-18 13:08:14 +01:00
Log : : setThreadLocalLogLevel ( COOLWSD : : LeastVerboseLogLevelSettableFromClient ) ;
2021-05-28 17:14:18 +02:00
LOG_INF ( " Thread-local logging level was set to [ "
2021-11-18 13:08:14 +01:00
< < COOLWSD : : LeastVerboseLogLevelSettableFromClient
2021-05-28 17:14:18 +02:00
< < " ] " ) ;
}
else
{
auto level = Poco : : Logger : : parseLevel ( tokens [ 1 ] ) ;
2021-05-31 13:00:21 +02:00
// Note that numerically the higher priority levels are lower in value.
if ( level > = leastVerboseAllowed & & level < = mostVerboseAllowed )
2021-05-28 17:14:18 +02:00
{
LOG_INF ( " Thread-local logging level being set to [ "
< < tokens [ 1 ]
< < " ] " ) ;
Log : : setThreadLocalLogLevel ( tokens [ 1 ] ) ;
}
else
{
LOG_WRN ( " Client tries to set logging level to [ "
< < tokens [ 1 ]
< < " ] which is outside of bounds [ "
2021-11-18 13:08:14 +01:00
< < COOLWSD : : LeastVerboseLogLevelSettableFromClient < < " , "
< < COOLWSD : : MostVerboseLogLevelSettableFromClient < < " ] " ) ;
2021-05-28 17:14:18 +02:00
}
}
}
catch ( const Poco : : Exception & e )
{
LOG_WRN ( " Exception while handling loggingleveloverride message: " < < e . message ( ) ) ;
}
}
}
}
2021-05-25 11:22:16 +02:00
else if ( tokens . equals ( 0 , " traceeventrecording " ) )
{
2021-11-18 13:08:14 +01:00
if ( COOLWSD : : getConfigValue < bool > ( " trace_event[@enable] " , false ) )
2021-05-25 11:22:16 +02:00
{
2021-07-07 11:35:14 +02:00
if ( tokens . size ( ) > 0 )
{
if ( tokens . equals ( 1 , " start " ) )
{
TraceEvent : : startRecording ( ) ;
LOG_INF ( " Trace Event recording in this WSD process turned on (might have been on already) " ) ;
}
else if ( tokens . equals ( 1 , " stop " ) )
{
TraceEvent : : stopRecording ( ) ;
LOG_INF ( " Trace Event recording in this WSD process turned off (might have been off already) " ) ;
}
}
2021-05-25 11:22:16 +02:00
forwardToChild ( firstLine , docBroker ) ;
}
return true ;
}
2023-06-19 18:02:16 +02:00
else if ( tokens . equals ( 0 , " a11ystate " ) )
{
if ( COOLWSD : : getConfigValue < bool > ( " accessibility.enable " , false ) )
{
return forwardToChild ( std : : string ( buffer , length ) , docBroker ) ;
}
}
2020-03-09 09:05:30 +01:00
else if ( tokens . equals ( 0 , " completefunction " ) )
2019-11-27 22:56:50 +01:00
{
2020-05-07 16:16:03 +02:00
return forwardToChild ( std : : string ( buffer , length ) , docBroker ) ;
2019-11-27 22:56:50 +01:00
}
2022-05-06 00:17:38 +02:00
else if ( tokens . equals ( 0 , " resetaccesstoken " ) )
{
if ( tokens . size ( ) ! = 2 )
{
LOG_ERR ( " Bad syntax for: " < < tokens [ 0 ] ) ;
sendTextFrameAndLogError ( " error: cmd=resetaccesstoken kind=syntax " ) ;
return false ;
}
_auth . resetAccessToken ( tokens [ 1 ] ) ;
return true ;
}
2023-12-06 04:05:59 +01:00
# if !MOBILEAPP && !WASMAPP
2023-11-26 16:05:42 +01:00
else if ( tokens . equals ( 0 , " switch_request " ) )
{
if ( tokens . size ( ) ! = 2 )
{
LOG_ERR ( " Bad syntax for: " < < tokens [ 0 ] ) ;
sendTextFrameAndLogError ( " error: cmd=switch_request kind=syntax " ) ;
return false ;
}
2023-11-26 16:19:53 +01:00
docBroker - > switchMode ( client_from_this ( ) , tokens [ 1 ] ) ;
2023-11-26 16:05:42 +01:00
return true ;
}
2023-12-06 04:05:59 +01:00
# endif // !MOBILEAPP && !WASMAPP
2021-10-18 16:41:12 +02:00
else if ( tokens . equals ( 0 , " outlinestate " ) | |
tokens . equals ( 0 , " downloadas " ) | |
tokens . equals ( 0 , " getchildid " ) | |
tokens . equals ( 0 , " gettextselection " ) | |
tokens . equals ( 0 , " paste " ) | |
tokens . equals ( 0 , " insertfile " ) | |
tokens . equals ( 0 , " key " ) | |
tokens . equals ( 0 , " textinput " ) | |
tokens . equals ( 0 , " windowkey " ) | |
tokens . equals ( 0 , " mouse " ) | |
tokens . equals ( 0 , " windowmouse " ) | |
tokens . equals ( 0 , " windowgesture " ) | |
tokens . equals ( 0 , " resetselection " ) | |
tokens . equals ( 0 , " saveas " ) | |
2023-01-19 16:19:53 +01:00
tokens . equals ( 0 , " exportas " ) | |
2021-10-18 16:41:12 +02:00
tokens . equals ( 0 , " selectgraphic " ) | |
tokens . equals ( 0 , " selecttext " ) | |
tokens . equals ( 0 , " windowselecttext " ) | |
tokens . equals ( 0 , " setpage " ) | |
tokens . equals ( 0 , " uno " ) | |
2023-07-31 16:28:27 +02:00
tokens . equals ( 0 , " urp " ) | |
2021-10-18 16:41:12 +02:00
tokens . equals ( 0 , " useractive " ) | |
tokens . equals ( 0 , " userinactive " ) | |
tokens . equals ( 0 , " paintwindow " ) | |
tokens . equals ( 0 , " windowcommand " ) | |
tokens . equals ( 0 , " asksignaturestatus " ) | |
tokens . equals ( 0 , " rendershapeselection " ) | |
tokens . equals ( 0 , " resizewindow " ) | |
tokens . equals ( 0 , " removetextcontext " ) | |
2023-04-07 10:58:09 +02:00
tokens . equals ( 0 , " rendersearchresult " ) | |
tokens . equals ( 0 , " geta11yfocusedparagraph " ) | |
tokens . equals ( 0 , " geta11ycaretposition " ) )
2016-05-17 01:05:22 +02:00
{
2020-03-09 09:05:30 +01:00
if ( tokens . equals ( 0 , " key " ) )
2017-09-11 19:59:38 +02:00
_keyEvents + + ;
2024-03-05 11:15:24 +01:00
if ( isEditable ( ) & & COOLProtocol : : tokenIndicatesDocumentModification ( tokens ) )
2023-02-04 22:59:08 +01:00
{
docBroker - > updateLastModifyingActivityTime ( ) ;
}
2016-12-05 13:18:33 +01:00
if ( ! filterMessage ( firstLine ) )
2016-05-17 01:05:22 +02:00
{
2016-10-16 19:43:44 +02:00
const std : : string dummyFrame = " dummymsg " ;
return forwardToChild ( dummyFrame , docBroker ) ;
2016-05-17 01:05:22 +02:00
}
2016-05-21 01:25:38 +02:00
else
{
2023-03-17 21:32:24 +01:00
return forwardToChild ( std : : string ( buffer , length ) , docBroker ) ;
2016-05-17 01:05:22 +02:00
}
}
2020-07-14 21:19:14 +02:00
else if ( tokens . equals ( 0 , " attemptlock " ) )
{
return attemptLock ( docBroker ) ;
}
2021-09-17 15:49:06 +02:00
else if ( tokens . equals ( 0 , " blockingcommandstatus " ) )
2021-09-13 22:02:43 +02:00
{
return forwardToChild ( std : : string ( buffer , length ) , docBroker ) ;
}
2023-05-28 14:50:06 +02:00
else if ( tokens . equals ( 0 , " toggletiledumping " ) )
{
return forwardToChild ( std : : string ( buffer , length ) , docBroker ) ;
}
2023-11-16 15:43:09 +01:00
# if !MOBILEAPP
else if ( tokens . equals ( 0 , " routetokensanitycheck " ) )
{
Admin : : instance ( ) . routeTokenSanityCheck ( ) ;
}
# endif
2020-07-15 13:07:23 +02:00
else
{
2022-06-04 15:16:04 +02:00
LOG_ERR ( " Session [ " < < getId ( ) < < " ] got unknown command [ " < < tokens [ 0 ] < < ' ] ' ) ;
2020-07-15 13:07:23 +02:00
sendTextFrameAndLogError ( " error: cmd= " + tokens [ 0 ] + " kind=unknown " ) ;
}
2016-05-21 01:25:38 +02:00
return false ;
2016-05-17 01:05:22 +02:00
}
2017-01-20 02:44:39 +01:00
bool ClientSession : : loadDocument ( const char * /*buffer*/ , int /*length*/ ,
2020-02-28 10:50:58 +01:00
const StringVector & tokens ,
2016-10-16 19:20:49 +02:00
const std : : shared_ptr < DocumentBroker > & docBroker )
2016-05-17 01:11:34 +02:00
{
2017-01-20 02:44:39 +01:00
if ( tokens . size ( ) < 2 )
2016-05-17 01:11:34 +02:00
{
2016-05-21 01:25:38 +02:00
// Failed loading ends connection.
2020-03-30 11:59:20 +02:00
sendTextFrameAndLogError ( " error: cmd=load kind=syntax " ) ;
2016-05-17 01:11:34 +02:00
return false ;
}
2019-11-12 10:50:33 +01:00
_viewLoadStart = std : : chrono : : steady_clock : : now ( ) ;
2016-12-22 00:27:37 +01:00
LOG_INF ( " Requesting document load from child. " ) ;
2016-05-17 01:11:34 +02:00
try
{
2019-05-22 05:33:26 +02:00
std : : string timestamp , doctemplate ;
2017-01-22 04:32:00 +01:00
int loadPart = - 1 ;
2019-05-22 05:33:26 +02:00
parseDocOptions ( tokens , loadPart , timestamp , doctemplate ) ;
2016-05-17 01:11:34 +02:00
2016-05-20 21:25:42 +02:00
std : : ostringstream oss ;
2021-01-23 15:11:31 +01:00
oss < < " load url= " < < docBroker - > getPublicUri ( ) . toString ( ) ;
2016-05-17 01:11:34 +02:00
2018-11-13 09:04:19 +01:00
if ( ! getUserId ( ) . empty ( ) & & ! getUserName ( ) . empty ( ) )
2016-08-30 08:08:07 +02:00
{
2016-10-26 16:35:40 +02:00
std : : string encodedUserId ;
2018-11-13 09:04:19 +01:00
Poco : : URI : : encode ( getUserId ( ) , " " , encodedUserId ) ;
2017-05-28 18:20:49 +02:00
oss < < " authorid= " < < encodedUserId ;
2024-03-11 13:20:03 +01:00
encodedUserId . clear ( ) ;
2021-11-18 13:08:14 +01:00
Poco : : URI : : encode ( COOLWSD : : anonymizeUsername ( getUserId ( ) ) , " " , encodedUserId ) ;
2018-10-20 18:23:18 +02:00
oss < < " xauthorid= " < < encodedUserId ;
2016-10-26 16:35:40 +02:00
2016-08-30 08:08:07 +02:00
std : : string encodedUserName ;
2018-11-13 09:04:19 +01:00
Poco : : URI : : encode ( getUserName ( ) , " " , encodedUserName ) ;
2017-05-28 18:20:49 +02:00
oss < < " author= " < < encodedUserName ;
2024-03-11 13:20:03 +01:00
encodedUserName . clear ( ) ;
2021-11-18 13:08:14 +01:00
Poco : : URI : : encode ( COOLWSD : : anonymizeUsername ( getUserName ( ) ) , " " , encodedUserName ) ;
2018-10-20 18:23:18 +02:00
oss < < " xauthor= " < < encodedUserName ;
2017-05-28 18:20:49 +02:00
}
2018-11-13 09:04:19 +01:00
if ( ! getUserExtraInfo ( ) . empty ( ) )
2017-05-28 18:20:49 +02:00
{
std : : string encodedUserExtraInfo ;
2018-11-13 09:04:19 +01:00
Poco : : URI : : encode ( getUserExtraInfo ( ) , " " , encodedUserExtraInfo ) ;
2019-06-16 20:42:11 +02:00
oss < < " authorextrainfo= " < < encodedUserExtraInfo ; //TODO: could this include PII?
2016-08-30 08:08:07 +02:00
}
2016-08-29 20:08:01 +02:00
2023-01-04 10:09:21 +01:00
if ( ! getUserPrivateInfo ( ) . empty ( ) )
{
std : : string encodedUserPrivateInfo ;
Poco : : URI : : encode ( getUserPrivateInfo ( ) , " " , encodedUserPrivateInfo ) ;
oss < < " authorprivateinfo= " < < encodedUserPrivateInfo ;
}
2017-03-30 16:59:59 +02:00
oss < < " readonly= " < < isReadOnly ( ) ;
2024-03-08 11:53:06 +01:00
if ( isAllowChangeComments ( ) )
{
oss < < " isAllowChangeComments=true " ;
}
2017-01-22 04:32:00 +01:00
if ( loadPart > = 0 )
{
oss < < " part= " < < loadPart ;
}
2016-05-20 21:25:42 +02:00
2018-11-13 09:04:19 +01:00
if ( getHaveDocPassword ( ) )
2017-01-22 04:32:00 +01:00
{
2018-11-13 09:04:19 +01:00
oss < < " password= " < < getDocPassword ( ) ;
2017-01-22 04:32:00 +01:00
}
2016-05-20 21:25:42 +02:00
2018-11-13 09:04:19 +01:00
if ( ! getLang ( ) . empty ( ) )
2017-03-24 12:34:32 +01:00
{
2018-11-13 09:04:19 +01:00
oss < < " lang= " < < getLang ( ) ;
2017-03-24 12:34:32 +01:00
}
2020-04-20 21:26:21 +02:00
if ( ! getDeviceFormFactor ( ) . empty ( ) )
{
oss < < " deviceFormFactor= " < < getDeviceFormFactor ( ) ;
}
2022-12-23 20:06:34 +01:00
if ( ! getTimezone ( ) . empty ( ) )
{
oss < < " timezone= " < < getTimezone ( ) ;
}
2020-10-14 11:16:47 +02:00
if ( ! getSpellOnline ( ) . empty ( ) )
{
oss < < " spellOnline= " < < getSpellOnline ( ) ;
}
2024-02-20 12:46:27 +01:00
if ( ! getTextDarkTheme ( ) . empty ( ) )
{
oss < < " textDarkTheme= " < < getTextDarkTheme ( ) ;
}
if ( ! getSpreadsheetDarkTheme ( ) . empty ( ) )
{
oss < < " spreadsheetDarkTheme= " < < getSpreadsheetDarkTheme ( ) ;
}
if ( ! getPresentationDarkTheme ( ) . empty ( ) )
{
oss < < " presentationDarkTheme= " < < getPresentationDarkTheme ( ) ;
}
if ( ! getDrawingDarkTheme ( ) . empty ( ) )
{
oss < < " drawingDarkTheme= " < < getDrawingDarkTheme ( ) ;
}
2018-11-13 09:04:19 +01:00
if ( ! getWatermarkText ( ) . empty ( ) )
2017-09-04 15:40:04 +02:00
{
std : : string encodedWatermarkText ;
2018-11-13 09:04:19 +01:00
Poco : : URI : : encode ( getWatermarkText ( ) , " " , encodedWatermarkText ) ;
2017-09-04 15:40:04 +02:00
oss < < " watermarkText= " < < encodedWatermarkText ;
2021-11-18 13:08:14 +01:00
oss < < " watermarkOpacity= " < < COOLWSD : : getConfigValue < double > ( " watermark.opacity " , 0.2 ) ;
2017-09-04 15:40:04 +02:00
}
2021-11-18 13:08:14 +01:00
if ( COOLWSD : : hasProperty ( " security.enable_macros_execution " ) )
2021-03-05 21:19:36 +01:00
{
oss < < " enableMacrosExecution= " < < std : : boolalpha
2021-11-18 13:08:14 +01:00
< < COOLWSD : : getConfigValue < bool > ( " security.enable_macros_execution " , false ) ;
2021-03-05 21:19:36 +01:00
}
2021-11-18 13:08:14 +01:00
if ( COOLWSD : : hasProperty ( " security.macro_security_level " ) )
2021-03-06 00:42:12 +01:00
{
2021-11-18 13:08:14 +01:00
oss < < " macroSecurityLevel= " < < COOLWSD : : getConfigValue < int > ( " security.macro_security_level " , 1 ) ;
2021-03-06 00:42:12 +01:00
}
2023-06-19 18:02:16 +02:00
if ( COOLWSD : : getConfigValue < bool > ( " accessibility.enable " , false ) )
2023-06-02 13:57:25 +02:00
{
2023-06-19 18:02:16 +02:00
oss < < " accessibilityState= " < < std : : boolalpha < < getAccessibilityState ( ) ;
2023-06-02 13:57:25 +02:00
}
2018-11-13 09:04:19 +01:00
if ( ! getDocOptions ( ) . empty ( ) )
2017-01-11 22:45:14 +01:00
{
2018-11-13 09:04:19 +01:00
oss < < " options= " < < getDocOptions ( ) ;
2017-01-11 22:45:14 +01:00
}
2017-01-22 04:32:00 +01:00
2019-05-22 05:33:26 +02:00
if ( _wopiFileInfo & & ! _wopiFileInfo - > getTemplateSource ( ) . empty ( ) )
{
oss < < " template= " < < _wopiFileInfo - > getTemplateSource ( ) ;
}
2021-02-17 22:10:04 +01:00
if ( ! getBatchMode ( ) . empty ( ) )
{
oss < < " batch= " < < getBatchMode ( ) ;
}
2022-08-03 04:04:49 +02:00
# if ENABLE_FEATURE_LOCK
2022-07-05 14:03:14 +02:00
sendLockedInfo ( ) ;
# endif
2023-08-24 12:00:34 +02:00
# if ENABLE_FEATURE_RESTRICTION
sendRestrictionInfo ( ) ;
# endif
2023-12-01 11:24:16 +01:00
return forwardToChild ( oss . str ( ) , docBroker ) ; ;
2016-05-17 01:11:34 +02:00
}
catch ( const Poco : : SyntaxException & )
{
2020-03-30 11:59:20 +02:00
sendTextFrameAndLogError ( " error: cmd=load kind=uriinvalid " ) ;
2016-05-17 01:11:34 +02:00
}
return false ;
}
2022-08-03 04:04:49 +02:00
# if ENABLE_FEATURE_LOCK
2022-07-05 14:03:14 +02:00
void ClientSession : : sendLockedInfo ( )
{
Poco : : JSON : : Object : : Ptr lockInfo = new Poco : : JSON : : Object ( ) ;
CommandControl : : LockManager : : setTranslationPath ( getLang ( ) ) ;
lockInfo - > set ( " IsLockedUser " , CommandControl : : LockManager : : isLockedUser ( ) ) ;
lockInfo - > set ( " IsLockReadOnly " , CommandControl : : LockManager : : isLockReadOnly ( ) ) ;
// Poco:Dynamic:Var does not support std::unordred_set so converted to std::vector
std : : vector < std : : string > lockedCommandList (
CommandControl : : LockManager : : getLockedCommandList ( ) . begin ( ) ,
CommandControl : : LockManager : : getLockedCommandList ( ) . end ( ) ) ;
lockInfo - > set ( " LockedCommandList " , lockedCommandList ) ;
lockInfo - > set ( " UnlockTitle " , CommandControl : : LockManager : : getUnlockTitle ( ) ) ;
lockInfo - > set ( " UnlockLink " , CommandControl : : LockManager : : getUnlockLink ( ) ) ;
lockInfo - > set ( " UnlockDescription " , CommandControl : : LockManager : : getUnlockDescription ( ) ) ;
lockInfo - > set ( " WriterHighlights " , CommandControl : : LockManager : : getWriterHighlights ( ) ) ;
lockInfo - > set ( " CalcHighlights " , CommandControl : : LockManager : : getCalcHighlights ( ) ) ;
lockInfo - > set ( " ImpressHighlights " , CommandControl : : LockManager : : getImpressHighlights ( ) ) ;
lockInfo - > set ( " DrawHighlights " , CommandControl : : LockManager : : getDrawHighlights ( ) ) ;
const Poco : : URI unlockImageUri = CommandControl : : LockManager : : getUnlockImageUri ( ) ;
if ( ! unlockImageUri . empty ( ) )
lockInfo - > set ( " UnlockImageUrlPath " , unlockImageUri . getPath ( ) ) ;
CommandControl : : LockManager : : resetTransalatioPath ( ) ;
std : : ostringstream ossLockInfo ;
lockInfo - > stringify ( ossLockInfo ) ;
const std : : string lockInfoString = ossLockInfo . str ( ) ;
LOG_TRC ( " Sending feature locking info to client: " < < lockInfoString ) ;
sendTextFrame ( " featurelock: " + lockInfoString ) ;
}
# endif
2023-08-24 12:00:34 +02:00
# if ENABLE_FEATURE_RESTRICTION
void ClientSession : : sendRestrictionInfo ( )
{
Poco : : JSON : : Object : : Ptr restrictionInfo = new Poco : : JSON : : Object ( ) ;
restrictionInfo - > set ( " IsRestrictedUser " ,
CommandControl : : RestrictionManager : : isRestrictedUser ( ) ) ;
// Poco:Dynamic:Var does not support std::unordred_set so converted to std::vector
std : : vector < std : : string > restrictedCommandList (
CommandControl : : RestrictionManager : : getRestrictedCommandList ( ) . begin ( ) ,
CommandControl : : RestrictionManager : : getRestrictedCommandList ( ) . end ( ) ) ;
restrictionInfo - > set ( " RestrictedCommandList " , restrictedCommandList ) ;
std : : ostringstream ossRestrictionInfo ;
restrictionInfo - > stringify ( ossRestrictionInfo ) ;
const std : : string restrictionInfoString = ossRestrictionInfo . str ( ) ;
LOG_TRC ( " Sending command restriction info to client: " < < restrictionInfoString ) ;
sendTextFrame ( " restrictedCommands: " + restrictionInfoString ) ;
}
# endif
2020-02-28 10:50:58 +01:00
bool ClientSession : : getCommandValues ( const char * buffer , int length , const StringVector & tokens ,
2016-10-16 19:20:49 +02:00
const std : : shared_ptr < DocumentBroker > & docBroker )
2016-05-16 13:37:02 +02:00
{
std : : string command ;
2017-01-20 02:44:39 +01:00
if ( tokens . size ( ) ! = 2 | | ! getTokenString ( tokens [ 1 ] , " command " , command ) )
2020-03-30 11:59:20 +02:00
return sendTextFrameAndLogError ( " error: cmd=commandvalues kind=syntax " ) ;
2016-05-16 13:37:02 +02:00
2017-01-17 16:42:31 +01:00
std : : string cmdValues ;
2022-10-25 15:16:02 +02:00
if ( docBroker - > hasTileCache ( ) & & docBroker - > tileCache ( ) . getTextStream ( TileCache : : StreamType : : CmdValues , command , cmdValues ) )
2016-05-21 01:25:38 +02:00
return sendTextFrame ( cmdValues ) ;
2016-05-16 13:37:02 +02:00
2016-10-16 19:43:44 +02:00
return forwardToChild ( std : : string ( buffer , length ) , docBroker ) ;
2016-05-16 13:37:02 +02:00
}
2020-02-28 10:50:58 +01:00
bool ClientSession : : sendFontRendering ( const char * buffer , int length , const StringVector & tokens ,
2016-10-16 19:20:49 +02:00
const std : : shared_ptr < DocumentBroker > & docBroker )
2016-05-16 13:37:02 +02:00
{
2016-11-28 00:49:17 +01:00
std : : string font , text ;
2017-01-20 02:44:39 +01:00
if ( tokens . size ( ) < 2 | |
2016-05-16 13:37:02 +02:00
! getTokenString ( tokens [ 1 ] , " font " , font ) )
{
2020-03-30 11:59:20 +02:00
return sendTextFrameAndLogError ( " error: cmd=renderfont kind=syntax " ) ;
2016-05-16 13:37:02 +02:00
}
2016-11-11 18:24:13 +01:00
getTokenString ( tokens [ 2 ] , " char " , text ) ;
2016-05-16 13:37:02 +02:00
2020-12-21 09:34:03 +01:00
if ( docBroker - > hasTileCache ( ) )
2016-05-16 13:37:02 +02:00
{
2021-11-13 12:49:35 +01:00
Blob cachedStream = docBroker - > tileCache ( ) . lookupCachedStream ( TileCache : : StreamType : : Font , font + text ) ;
2021-11-08 20:48:14 +01:00
if ( cachedStream )
2020-12-21 09:34:03 +01:00
{
const std : : string response = " renderfont: " + tokens . cat ( ' ' , 1 ) + ' \n ' ;
2021-11-08 20:48:14 +01:00
return sendBlob ( response , cachedStream ) ;
2020-12-21 09:34:03 +01:00
}
2016-05-16 13:37:02 +02:00
}
2016-10-16 19:43:44 +02:00
return forwardToChild ( std : : string ( buffer , length ) , docBroker ) ;
2016-05-16 13:37:02 +02:00
}
2020-02-28 10:50:58 +01:00
bool ClientSession : : sendTile ( const char * /*buffer*/ , int /*length*/ , const StringVector & tokens ,
2016-10-16 19:20:49 +02:00
const std : : shared_ptr < DocumentBroker > & docBroker )
2016-05-16 13:37:02 +02:00
{
try
{
2022-05-25 11:33:27 +02:00
docBroker - > handleTileRequest ( tokens , true , client_from_this ( ) ) ;
2016-05-16 13:37:02 +02:00
}
catch ( const std : : exception & exc )
{
2016-12-22 00:27:37 +01:00
LOG_ERR ( " Failed to process tile command: " < < exc . what ( ) ) ;
2020-03-30 11:59:20 +02:00
return sendTextFrameAndLogError ( " error: cmd=tile kind=invalid " ) ;
2016-05-16 13:37:02 +02:00
}
2016-05-21 01:25:38 +02:00
return true ;
2016-05-16 13:37:02 +02:00
}
2020-02-28 10:50:58 +01:00
bool ClientSession : : sendCombinedTiles ( const char * /*buffer*/ , int /*length*/ , const StringVector & tokens ,
2016-10-16 19:20:49 +02:00
const std : : shared_ptr < DocumentBroker > & docBroker )
2016-05-16 13:37:02 +02:00
{
2016-05-22 17:45:28 +02:00
try
2016-05-16 13:37:02 +02:00
{
2018-02-07 10:17:59 +01:00
TileCombined tileCombined = TileCombined : : parse ( tokens ) ;
2020-01-02 22:11:54 +01:00
tileCombined . setNormalizedViewId ( getCanonicalViewId ( ) ) ;
2022-06-15 17:37:16 +02:00
if ( tileCombined . hasDuplicates ( ) )
{
LOG_ERR ( " Dangerous, tilecombine with duplicates is not acceptable " ) ;
return sendTextFrameAndLogError ( " error: cmd=tile kind=invalid " ) ;
}
2022-05-25 11:33:27 +02:00
docBroker - > handleTileCombinedRequest ( tileCombined , true , client_from_this ( ) ) ;
2016-05-16 13:37:02 +02:00
}
2016-05-22 17:45:28 +02:00
catch ( const std : : exception & exc )
2016-05-16 13:37:02 +02:00
{
2016-12-22 00:27:37 +01:00
LOG_ERR ( " Failed to process tilecombine command: " < < exc . what ( ) ) ;
2019-07-03 06:29:43 +02:00
// Be forgiving and log instead of disconnecting.
2020-03-30 11:59:20 +02:00
// return sendTextFrameAndLogError("error: cmd=tile kind=invalid");
2016-05-16 13:37:02 +02:00
}
2016-05-21 01:25:38 +02:00
return true ;
2016-05-16 13:37:02 +02:00
}
2016-10-16 19:43:44 +02:00
bool ClientSession : : forwardToChild ( const std : : string & message ,
2016-10-16 19:20:49 +02:00
const std : : shared_ptr < DocumentBroker > & docBroker )
2016-10-08 20:25:27 +02:00
{
2024-02-22 05:12:57 +01:00
const bool binary = message . starts_with ( " paste " ) | | message . starts_with ( " urp " ) ;
2023-07-13 12:57:34 +02:00
return docBroker - > forwardToChild ( client_from_this ( ) , message , binary ) ;
2016-10-08 20:25:27 +02:00
}
2016-12-05 13:18:33 +01:00
bool ClientSession : : filterMessage ( const std : : string & message ) const
{
bool allowed = true ;
2022-03-30 03:37:57 +02:00
StringVector tokens ( StringVector : : tokenize ( message , ' ' ) ) ;
2016-12-13 12:30:43 +01:00
// Set allowed flag to false depending on if particular WOPI properties are set
2020-03-09 09:05:30 +01:00
if ( tokens . equals ( 0 , " downloadas " ) )
2016-12-13 12:30:43 +01:00
{
std : : string id ;
wsd: fix crash when downloadas has not enough parameters
==11898==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x0000007c4f87 bp 0x7fffe45dfe90 sp 0x7fffe45df608 T0)
==11898==The signal is caused by a READ memory access.
==11898==Hint: address points to the zero page.
#0 0x7c4f86 in AddressIsPoisoned lode/packages/llvm-472c6ef8b0f53061b049039f9775ab127beafbe4.src/compiler-rt/lib/asan/asan_mapping.h:397
#1 0x7c4f86 in __asan::QuickCheckForUnpoisonedRegion(unsigned long, unsigned long) lode/packages/llvm-472c6ef8b0f53061b049039f9775ab127beafbe4.src/compiler-rt/lib/asan/asan_interceptors_memintrinsics.h:31
#2 0x816436 in MemcmpInterceptorCommon(void*, int (*)(void const*, void const*, unsigned long), void const*, void const*, unsigned long) lode/packages/llvm-472c6ef8b0f53061b049039f9775ab127beafbe4.src/compiler-rt/lib/asan/../sanitizer_common/sanitizer_common_interceptors.inc:834
#3 0x816d38 in memcmp lode/packages/llvm-472c6ef8b0f53061b049039f9775ab127beafbe4.src/compiler-rt/lib/asan/../sanitizer_common/sanitizer_common_interceptors.inc:866
#4 0x7f1964437595 in std::char_traits<char>::compare(char const*, char const*, unsigned long) lode/packages/gccbuild/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/char_traits.h:310
#5 0x7f1964437595 in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::compare(unsigned long, unsigned long, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) const lode/packages/gccbuild/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/basic_string.tcc:1391
#6 0x18e206d in LOOLProtocol::getTokenString(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&) common/Protocol.cpp:141:19
#7 0x117cc0a in ClientSession::filterMessage(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) const wsd/ClientSession.cpp:940:13
#8 0x116b832 in ClientSession::_handleInput(char const*, int) wsd/ClientSession.cpp:741:14
#9 0x18f70d0 in Session::handleMessage(bool, WSOpCode, std::vector<char, std::allocator<char> >&) common/Session.cpp:230:13
Change-Id: I0c7da6c02ac62bf0bc99557517fc7c517917046c
Reviewed-on: https://gerrit.libreoffice.org/c/online/+/89229
Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice@gmail.com>
Reviewed-by: Michael Meeks <michael.meeks@collabora.com>
2020-02-21 16:27:58 +01:00
if ( tokens . size ( ) > = 3 & & getTokenString ( tokens [ 2 ] , " id " , id ) )
2016-12-13 12:30:43 +01:00
{
2018-11-21 09:07:52 +01:00
if ( id = = " print " & & _wopiFileInfo & & _wopiFileInfo - > getDisablePrint ( ) )
2016-12-13 12:30:43 +01:00
{
allowed = false ;
LOG_WRN ( " WOPI host has disabled print for this session " ) ;
}
2018-11-21 09:07:52 +01:00
else if ( id = = " export " & & _wopiFileInfo & & _wopiFileInfo - > getDisableExport ( ) )
2016-12-13 12:30:43 +01:00
{
allowed = false ;
LOG_WRN ( " WOPI host has disabled export for this session " ) ;
}
2024-05-01 18:23:10 +02:00
else if ( id = = " slideshow " & & _wopiFileInfo & &
( _wopiFileInfo - > getDisableExport ( ) | | ! _wopiFileInfo - > getWatermarkText ( ) . empty ( ) ) )
{
allowed = false ;
LOG_WRN ( " WOPI host has disabled slideshow for this session " ) ;
}
2016-12-13 12:30:43 +01:00
}
else
{
2018-06-11 04:30:42 +02:00
allowed = false ;
LOG_WRN ( " No value of id in downloadas message " ) ;
2016-12-13 12:30:43 +01:00
}
}
2023-02-04 23:05:51 +01:00
else if ( tokens . equals ( 0 , " gettextselection " ) )
2016-12-13 13:13:51 +01:00
{
2023-02-04 23:05:51 +01:00
// Copying/pasting *within* the document is fine,
// so keep .uno:Copy and .uno:Paste, but exporting is not.
2018-11-21 09:07:52 +01:00
if ( _wopiFileInfo & & _wopiFileInfo - > getDisableCopy ( ) )
2016-12-13 13:13:51 +01:00
{
allowed = false ;
2017-05-25 10:41:33 +02:00
LOG_WRN ( " WOPI host has disabled copying from the document " ) ;
2016-12-13 13:13:51 +01:00
}
}
2016-12-05 13:18:33 +01:00
return allowed ;
}
2020-07-01 10:34:08 +02:00
void ClientSession : : setReadOnly ( bool bVal )
2016-10-19 16:52:53 +02:00
{
2020-07-01 10:34:08 +02:00
Session : : setReadOnly ( bVal ) ;
2016-10-19 16:52:53 +02:00
// Also inform the client
2020-07-01 10:34:08 +02:00
const std : : string sPerm = bVal ? " readonly " : " edit " ;
sendTextFrame ( " perm: " + sPerm ) ;
}
2021-04-20 12:06:07 +02:00
void ClientSession : : sendFileMode ( const bool readOnly , const bool editComments )
{
std : : string result = " filemode:{ \" readOnly \" : " ;
result + = readOnly ? " true " : " false " ;
result + = " , \" editComment \" : " ;
result + = editComments ? " true " : " false " ;
result + = " } " ;
sendTextFrame ( result ) ;
}
2020-07-01 10:34:08 +02:00
void ClientSession : : setLockFailed ( const std : : string & sReason )
{
2023-02-21 02:18:12 +01:00
// TODO: make this "read-only" a special one with a notification (infobar? balloon tip?)
// and a button to unlock
2020-07-01 10:34:08 +02:00
_isLockFailed = true ;
2022-11-13 17:05:17 +01:00
setReadOnly ( true ) ;
2020-07-01 10:34:08 +02:00
sendTextFrame ( " lockfailed: " + sReason ) ;
2016-10-19 16:52:53 +02:00
}
2020-07-14 21:19:14 +02:00
bool ClientSession : : attemptLock ( const std : : shared_ptr < DocumentBroker > & docBroker )
{
if ( ! isReadOnly ( ) )
return true ;
// We are only allowed to change into edit mode if the read-only mode is because of failed lock
if ( ! _isLockFailed )
return false ;
std : : string failReason ;
const bool bResult = docBroker - > attemptLock ( * this , failReason ) ;
if ( bResult )
setReadOnly ( false ) ;
else
sendTextFrame ( " lockfailed: " + failReason ) ;
return bResult ;
}
2020-03-06 18:43:46 +01:00
bool ClientSession : : hasQueuedMessages ( ) const
2017-03-06 17:26:52 +01:00
{
2020-03-06 18:43:46 +01:00
return _senderQueue . size ( ) > 0 ;
2017-03-06 17:26:52 +01:00
}
2021-03-07 18:57:13 +01:00
void ClientSession : : writeQueuedMessages ( std : : size_t capacity )
2016-12-14 01:20:05 +01:00
{
2022-06-04 15:16:04 +02:00
LOG_TRC ( " performing writes, up to " < < capacity < < " bytes " ) ;
2016-12-14 01:20:05 +01:00
2017-03-06 17:26:52 +01:00
std : : shared_ptr < Message > item ;
2021-03-07 18:57:13 +01:00
std : : size_t wrote = 0 ;
try
2016-12-14 01:20:05 +01:00
{
2021-03-07 18:57:13 +01:00
// Drain the queue, for efficient communication.
2021-03-30 18:07:42 +02:00
while ( capacity > wrote & & _senderQueue . dequeue ( item ) & & item )
2016-12-14 01:20:05 +01:00
{
2017-03-30 02:38:41 +02:00
const std : : vector < char > & data = item - > data ( ) ;
2021-03-07 18:57:13 +01:00
const auto size = data . size ( ) ;
assert ( size & & " Zero-sized messages must never be queued for sending. " ) ;
2017-03-06 17:26:52 +01:00
if ( item - > isBinary ( ) )
2016-12-14 01:20:05 +01:00
{
2021-03-07 18:57:13 +01:00
Session : : sendBinaryFrame ( data . data ( ) , size ) ;
2016-12-14 01:20:05 +01:00
}
2017-03-06 17:26:52 +01:00
else
2016-12-14 01:20:05 +01:00
{
2021-03-07 18:57:13 +01:00
Session : : sendTextFrame ( data . data ( ) , size ) ;
2016-12-14 01:20:05 +01:00
}
2021-03-07 18:57:13 +01:00
wrote + = size ;
2022-06-04 15:16:04 +02:00
LOG_TRC ( " wrote " < < size < < " , total " < < wrote < < " bytes " ) ;
2016-12-14 01:20:05 +01:00
}
2021-03-07 18:57:13 +01:00
}
catch ( const std : : exception & ex )
{
2022-06-04 15:16:04 +02:00
LOG_ERR ( " Failed to send message " < < ( item ? item - > abbr ( ) : " <empty-item> " )
< < " to client: " < < ex . what ( ) ) ;
2016-12-14 01:20:05 +01:00
}
2022-06-04 15:16:04 +02:00
LOG_TRC ( " performed write, wrote " < < wrote < < " bytes " ) ;
2016-12-14 01:20:05 +01:00
}
2021-11-03 15:31:18 +01:00
// NB. also see browser/src/map/Clipboard.js that does this in JS for stubs.
2021-10-22 09:20:19 +02:00
// See also ClientSession::preProcessSetClipboardPayload() which removes the
2024-01-24 11:14:45 +01:00
// <div id="meta-origin"...> tag added here.
2020-06-02 09:44:08 +02:00
void ClientSession : : postProcessCopyPayload ( const std : : shared_ptr < Message > & payload )
2019-07-04 17:47:03 +02:00
{
// Insert our meta origin if we can
2024-02-01 04:56:11 +01:00
payload - > rewriteDataBody ( [ this ] ( std : : vector < char > & data ) {
2024-03-21 09:13:11 +01:00
if ( Util : : findInVector ( data , " clipboardcontent: content \n text/plain " ) = = 0 )
{
// Single format and it's plain text (not HTML): no need to rewrite anything.
return false ;
}
cool#8465 clipboard: improve handling of plain text copy, simple case
Currently the current selection is always requested as HTML by the
browser, and then we ask the browser to convert it to plain text.
The problem is that e.g. Writer can produce much better plain text from
its model, compared to the plain text by the browser, e.g. bullet
characters for bullet points.
Fix the problem by:
- CanvasTileLayer.js, _onTextSelectionMsg(): requesting both HTML and
plain text. Use ',' as a separator, as that's already established,
e.g. the HTTP Accept header does that already
- Switching the textselectioncontent protocol message from just HTML to
JSON that contains both HTML and plain text. This is produced in
ChildSession::getTextSelection() and parsed in CanvasTileLayer.js,
_onMessage()
- Clipboard.js, setTextSelectionHTML(): allowing setting both HTML and
plain text.
- ClientSession::postProcessCopyPayload(): knowing if the content to be
processed is HTML-in-JSON or just HTML, do additional escaping in the
JSON / textselectioncontent case, but leave the other clipboardcontent
case unchanged.
So far this only handles the simple case, the behavior for complex
selections are left unchanged for now. The payload is also unchanged
when a single format is requested, as many tests depend on test.
Signed-off-by: Miklos Vajna <vmiklos@collabora.com>
Change-Id: I2fe1378a8d50b7901ac9e808eb78858cd8ff8575
2024-03-07 14:18:43 +01:00
bool json = Util : : findInVector ( data , " textselectioncontent: \n { " ) = = 0 ;
cool#8465 clipboard: improve handling of plain text copy, complex case
In case the selection is complex (not simple), we used to just request
HTML, and then the browser converted that to plain text, which has the
downsides already mentioned in commit
7f9de46688a64b42ba8f65cceb9fe2c6ddab89ef (cool#8465 clipboard: improve
handling of plain text copy, simple case, 2024-03-08).
Steps to support this:
1) Clipboard.js now asks for the text/html,text/plain;charset=utf-8 MIME
types.
2) wsd: ClientRequestDispatcher::handleClipboardRequest() now maps this
to DocumentBroker::CLIP_REQUEST_GET_HTML_PLAIN_ONLY
3) ClientSession::handleClipboardRequest() maps this to the HTML+plain
text MIME type list.
4) kit: ChildSession::getClipboard() is now improved to take a list of
MIME types, not just 1 or everything.
5) kit: ChildSession::getClipboard() now emits JSON in case not all, but
multiple MIME types are requested.
6) wsd: ClientSession::postProcessCopyPayload() now knows how to
postprocess clipboardcontent messages, which may or may not be JSON
(it's JSON if more formats are requested explicitly, leaving the 1
format or all format cases unchanged)
7) Control.DownloadProgress.js now handles the case when we get JSON and
sets the core-provided plain text next to the HTML.
Leave the handling of non-JSON case in, because this means we can
copy from an old COOL server to a new one.
Note that this approach has the benefit that once the clipboard marker
is inserted, the length of the text/html format would change, which
means we can't parse the clipboard data till the marker is removed.
Emitting JSON for html+text means adding the marker keeps the ability to
parse the HTML and the plain text part of the clipboard in JS.
Signed-off-by: Miklos Vajna <vmiklos@collabora.com>
Change-Id: I67a1f669e8a638d34cc25a2f288a7b30884b9892
2024-03-19 12:12:16 +01:00
if ( ! json )
{
json = Util : : findInVector ( data , " clipboardcontent: content \n { " ) = = 0 ;
}
2024-01-22 14:55:45 +01:00
std : : size_t pos = Util : : findInVector ( data , " <body " ) ;
if ( pos ! = std : : string : : npos )
2024-01-19 12:56:31 +01:00
{
2024-01-22 14:55:45 +01:00
pos = Util : : findInVector ( data , " > " , pos ) ;
2024-01-19 12:56:31 +01:00
}
2024-01-15 16:07:29 +01:00
2024-01-24 11:14:45 +01:00
// cf. TileLayer.js /_dataTransferToDocument/
2024-01-22 14:55:45 +01:00
if ( pos ! = std : : string : : npos )
2024-01-15 16:07:29 +01:00
{
const std : : string meta = getClipboardURI ( ) ;
LOG_TRC ( " Inject clipboard cool origin of ' " < < meta < < " ' " ) ;
std : : string origin = " <div id= \" meta-origin \" data-coolorigin= \" " + meta + " \" > \n " ;
cool#8465 clipboard: improve handling of plain text copy, simple case
Currently the current selection is always requested as HTML by the
browser, and then we ask the browser to convert it to plain text.
The problem is that e.g. Writer can produce much better plain text from
its model, compared to the plain text by the browser, e.g. bullet
characters for bullet points.
Fix the problem by:
- CanvasTileLayer.js, _onTextSelectionMsg(): requesting both HTML and
plain text. Use ',' as a separator, as that's already established,
e.g. the HTTP Accept header does that already
- Switching the textselectioncontent protocol message from just HTML to
JSON that contains both HTML and plain text. This is produced in
ChildSession::getTextSelection() and parsed in CanvasTileLayer.js,
_onMessage()
- Clipboard.js, setTextSelectionHTML(): allowing setting both HTML and
plain text.
- ClientSession::postProcessCopyPayload(): knowing if the content to be
processed is HTML-in-JSON or just HTML, do additional escaping in the
JSON / textselectioncontent case, but leave the other clipboardcontent
case unchanged.
So far this only handles the simple case, the behavior for complex
selections are left unchanged for now. The payload is also unchanged
when a single format is requested, as many tests depend on test.
Signed-off-by: Miklos Vajna <vmiklos@collabora.com>
Change-Id: I2fe1378a8d50b7901ac9e808eb78858cd8ff8575
2024-03-07 14:18:43 +01:00
if ( json )
{
origin = " <div id= \\ \" meta-origin \\ \" data-coolorigin= \\ \" " + meta + " \\ \" > \\ n " ;
}
2024-01-22 14:55:45 +01:00
data . insert ( data . begin ( ) + pos + strlen ( " > " ) , origin . begin ( ) , origin . end ( ) ) ;
2024-01-15 16:07:29 +01:00
2024-01-19 12:56:31 +01:00
const char * end = " </body> " ;
2024-01-22 14:55:45 +01:00
pos = Util : : findInVector ( data , end ) ;
if ( pos ! = std : : string : : npos )
2024-01-15 16:07:29 +01:00
{
origin = " </div> " ;
2024-01-22 14:55:45 +01:00
data . insert ( data . begin ( ) + pos , origin . begin ( ) , origin . end ( ) ) ;
2024-01-15 16:07:29 +01:00
}
return true ;
}
else
{
2024-02-02 14:31:19 +01:00
LOG_DBG ( " Missing <body> in textselectioncontent/clipboardcontent payload: "
< < Util : : dumpHex ( data ) ) ;
2024-01-15 16:07:29 +01:00
return false ;
}
} ) ;
2019-07-04 17:47:03 +02:00
}
2022-06-06 00:37:03 +02:00
bool ClientSession : : handleKitToClientMessage ( const std : : shared_ptr < Message > & payload )
2017-01-22 02:31:22 +01:00
{
2022-06-04 15:16:04 +02:00
LOG_TRC ( " handling kit-to-client [ " < < payload - > abbr ( ) < < ' ] ' ) ;
2017-01-23 05:19:04 +01:00
const std : : string & firstLine = payload - > firstLine ( ) ;
2017-01-22 03:38:11 +01:00
2018-02-07 10:17:59 +01:00
const std : : shared_ptr < DocumentBroker > docBroker = _docBroker . lock ( ) ;
2017-01-22 03:38:11 +01:00
if ( ! docBroker )
{
LOG_ERR ( " No DocBroker to handle kit-to-client message: " < < firstLine ) ;
return false ;
}
2019-05-24 08:57:06 +02:00
const bool isConvertTo = static_cast < bool > ( _saveAsSocket ) ;
2024-03-17 23:11:02 +01:00
if ( ! Util : : isMobileApp ( ) )
COOLWSD : : dumpOutgoingTrace ( docBroker - > getJailId ( ) , getId ( ) , firstLine ) ;
2017-01-22 03:38:11 +01:00
2017-01-23 05:19:04 +01:00
const auto & tokens = payload - > tokens ( ) ;
2021-10-18 16:41:12 +02:00
if ( tokens . equals ( 0 , " unocommandresult: " ) )
2017-01-22 03:38:11 +01:00
{
2022-06-06 00:37:03 +02:00
LOG_INF ( " Command: " < < firstLine ) ;
const std : : string stringJSON = payload - > jsonString ( ) ;
if ( ! stringJSON . empty ( ) )
2017-01-22 03:38:11 +01:00
{
2022-01-06 17:26:15 +01:00
try
2017-01-22 03:38:11 +01:00
{
2022-01-06 17:26:15 +01:00
Poco : : JSON : : Parser parser ;
const Poco : : Dynamic : : Var parsedJSON = parser . parse ( stringJSON ) ;
const auto & object = parsedJSON . extract < Poco : : JSON : : Object : : Ptr > ( ) ;
if ( object - > get ( " commandName " ) . toString ( ) = = " .uno:Save " )
2017-01-22 03:38:11 +01:00
{
2022-01-06 17:26:15 +01:00
// Save to Storage and log result.
2023-03-24 12:20:41 +01:00
docBroker - > handleSaveResponse ( client_from_this ( ) , object ) ;
2017-06-07 16:32:58 +02:00
2022-01-06 17:26:15 +01:00
if ( ! isCloseFrame ( ) )
forwardToClient ( payload ) ;
2017-06-07 16:32:58 +02:00
2022-01-06 17:26:15 +01:00
return true ;
}
}
catch ( const std : : exception & exception )
{
LOG_ERR ( " unocommandresult parsing failure: " < < exception . what ( ) ) ;
2017-01-22 03:38:11 +01:00
}
}
else
{
2022-06-06 00:37:03 +02:00
LOG_WRN ( " Expected json unocommandresult. Ignoring: " < < firstLine ) ;
2017-01-22 03:38:11 +01:00
}
}
2021-10-18 16:41:12 +02:00
else if ( tokens . equals ( 0 , " error: " ) )
2017-01-22 03:38:11 +01:00
{
std : : string errorCommand ;
std : : string errorKind ;
if ( getTokenString ( tokens [ 1 ] , " cmd " , errorCommand ) & &
getTokenString ( tokens [ 2 ] , " kind " , errorKind ) )
{
if ( errorCommand = = " load " )
{
2021-02-17 12:52:15 +01:00
LOG_ERR ( " Document load failed: " < < errorKind ) ;
2017-01-22 03:38:11 +01:00
if ( errorKind = = " passwordrequired:to-view " | |
errorKind = = " passwordrequired:to-modify " | |
errorKind = = " wrongpassword " )
{
2019-05-24 08:57:06 +02:00
if ( isConvertTo )
{
2023-10-26 09:20:29 +02:00
http : : Response response ( http : : StatusCode : : Unauthorized ) ;
2019-05-24 08:57:06 +02:00
response . set ( " X-ERROR-KIND " , errorKind ) ;
2024-03-18 11:23:41 +01:00
_saveAsSocket - > send ( response ) ;
2019-05-24 08:57:06 +02:00
// Conversion failed, cleanup fake session.
LOG_TRC ( " Removing save-as ClientSession after conversion error. " ) ;
// Remove us.
2022-11-24 22:23:29 +01:00
docBroker - > removeSession ( client_from_this ( ) ) ;
2019-05-24 08:57:06 +02:00
// Now terminate.
docBroker - > stop ( " Aborting saveas handler. " ) ;
}
else
{
forwardToClient ( payload ) ;
}
2017-01-22 03:38:11 +01:00
return false ;
}
}
2018-07-11 11:32:29 +02:00
else
{
2021-02-17 12:52:15 +01:00
LOG_ERR ( errorCommand < < " error failure: " < < errorKind ) ;
2018-07-11 11:32:29 +02:00
}
2017-01-22 03:38:11 +01:00
}
}
2021-10-18 16:41:12 +02:00
else if ( tokens . equals ( 0 , " curpart: " ) & & tokens . size ( ) = = 2 )
2017-01-22 03:38:11 +01:00
{
//TODO: Should forward to client?
int curPart ;
return getTokenInteger ( tokens [ 1 ] , " part " , curPart ) ;
}
2021-10-18 16:41:12 +02:00
else if ( tokens . equals ( 0 , " setpart: " ) & & tokens . size ( ) = = 2 )
2018-05-30 19:35:13 +02:00
{
2018-06-19 16:15:37 +02:00
if ( ! _isTextDocument )
2018-07-10 14:12:11 +02:00
{
2018-06-19 16:15:37 +02:00
int setPart ;
if ( getTokenInteger ( tokens [ 1 ] , " part " , setPart ) )
{
_clientSelectedPart = setPart ;
}
else if ( stringToInteger ( tokens [ 1 ] , setPart ) )
{
_clientSelectedPart = setPart ;
}
else
return false ;
2018-10-22 01:32:41 +02:00
}
2018-05-30 19:35:13 +02:00
}
2019-02-12 12:16:40 +01:00
# if !MOBILEAPP
2023-01-19 16:19:53 +01:00
else if ( tokens . size ( ) = = 3 & & ( tokens . equals ( 0 , " saveas: " ) | | tokens . equals ( 0 , " exportas: " ) ) )
2017-01-22 03:38:11 +01:00
{
2023-01-19 16:19:53 +01:00
bool isExportAs = tokens . equals ( 0 , " exportas: " ) ;
2017-10-26 12:12:13 +02:00
2017-10-20 18:12:05 +02:00
std : : string encodedURL ;
if ( ! getTokenString ( tokens [ 1 ] , " url " , encodedURL ) )
2017-01-22 03:38:11 +01:00
{
LOG_ERR ( " Bad syntax for: " < < firstLine ) ;
2017-10-26 12:12:13 +02:00
// we must not return early with convert-to so that we clean up
// the session
if ( ! isConvertTo )
{
2020-03-30 11:59:20 +02:00
sendTextFrameAndLogError ( " error: cmd=saveas kind=syntax " ) ;
2017-10-26 12:12:13 +02:00
return false ;
}
2017-01-22 03:38:11 +01:00
}
2017-10-20 18:12:05 +02:00
std : : string encodedWopiFilename ;
2017-10-26 12:12:13 +02:00
if ( ! isConvertTo & & ! getTokenString ( tokens [ 2 ] , " filename " , encodedWopiFilename ) )
2017-10-20 18:12:05 +02:00
{
LOG_ERR ( " Bad syntax for: " < < firstLine ) ;
2020-03-30 11:59:20 +02:00
sendTextFrameAndLogError ( " error: cmd=saveas kind=syntax " ) ;
2017-10-20 18:12:05 +02:00
return false ;
}
2019-03-19 15:00:38 +01:00
// Save-as completed, inform the ClientSession.
std : : string wopiFilename ;
2017-10-20 18:12:05 +02:00
Poco : : URI : : decode ( encodedWopiFilename , wopiFilename ) ;
2019-03-19 15:00:38 +01:00
// URI constructor implicitly decodes when it gets std::string as param
Poco : : URI resultURL ( encodedURL ) ;
2020-05-05 19:38:04 +02:00
// Prepend the jail path in the normal (non-nocaps) case
2021-11-18 13:08:14 +01:00
if ( resultURL . getScheme ( ) = = " file " & & ! COOLWSD : : NoCapsForKit )
2017-01-22 03:38:11 +01:00
{
2020-09-30 17:18:22 +02:00
std : : string relative ;
2023-03-16 11:54:21 +01:00
if ( isConvertTo | | isExportAs )
2020-09-30 17:18:22 +02:00
Poco : : URI : : decode ( resultURL . getPath ( ) , relative ) ;
else
relative = resultURL . getPath ( ) ;
2017-10-20 18:12:05 +02:00
if ( relative . size ( ) > 0 & & relative [ 0 ] = = ' / ' )
relative = relative . substr ( 1 ) ;
2020-05-05 19:38:04 +02:00
// Rewrite file:// URLs to be visible to the outside world.
2017-10-20 18:12:05 +02:00
const Path path ( docBroker - > getJailRoot ( ) , relative ) ;
2017-01-22 03:38:11 +01:00
if ( Poco : : File ( path ) . exists ( ) )
2019-03-20 08:50:14 +01:00
{
2020-09-30 17:27:45 +02:00
if ( ! isConvertTo )
{
// Encode path for special characters (i.e '%') since Poco::URI::setPath implicitly decodes the input param
std : : string encodedPath ;
Poco : : URI : : encode ( path . toString ( ) , " " , encodedPath ) ;
2019-03-20 08:50:14 +01:00
2020-09-30 17:27:45 +02:00
resultURL . setPath ( encodedPath ) ;
}
else
{
resultURL . setPath ( path . toString ( ) ) ;
}
2017-01-22 03:38:11 +01:00
}
else
{
// Blank for failure.
2017-10-20 18:12:05 +02:00
LOG_DBG ( " SaveAs produced no output in ' " < < path . toString ( ) < < " ', producing blank url. " ) ;
resultURL . clear ( ) ;
2017-01-22 03:38:11 +01:00
}
}
2017-10-20 18:12:05 +02:00
LOG_TRC ( " Save-as URL: " < < resultURL . toString ( ) ) ;
2017-10-26 12:12:13 +02:00
if ( ! isConvertTo )
2017-03-18 21:36:32 +01:00
{
2017-10-20 18:12:05 +02:00
// Normal SaveAs - save to Storage and log result.
if ( resultURL . getScheme ( ) = = " file " & & ! resultURL . getPath ( ) . empty ( ) )
{
2017-10-25 14:09:27 +02:00
// this also sends the saveas: result
2017-11-06 12:44:37 +01:00
LOG_TRC ( " Save-as path: " < < resultURL . getPath ( ) ) ;
2022-11-25 13:47:37 +01:00
docBroker - > uploadAsToStorage ( client_from_this ( ) , resultURL . getPath ( ) , wopiFilename ,
2023-01-19 16:19:53 +01:00
false , isExportAs ) ;
2017-10-20 18:12:05 +02:00
}
2017-10-25 14:09:27 +02:00
else
2020-03-30 11:59:20 +02:00
sendTextFrameAndLogError ( " error: cmd=storage kind=savefailed " ) ;
2017-10-20 18:12:05 +02:00
}
else
{
// using the convert-to REST API
2017-03-18 21:36:32 +01:00
// TODO: Send back error when there is no output.
if ( ! resultURL . getPath ( ) . empty ( ) )
{
2020-09-30 17:35:17 +02:00
LOG_TRC ( " Sending file: " < < resultURL . getPath ( ) ) ;
2017-08-02 18:55:43 +02:00
const std : : string fileName = Poco : : Path ( resultURL . getPath ( ) ) . getFileName ( ) ;
2023-10-22 23:23:41 +02:00
http : : Response response ( http : : StatusCode : : OK ) ;
2024-05-08 12:04:54 +02:00
FileServerRequestHandler : : hstsHeaders ( response ) ;
2017-08-02 18:55:43 +02:00
if ( ! fileName . empty ( ) )
2020-05-24 15:10:18 +02:00
response . set ( " Content-Disposition " , " attachment; filename= \" " + fileName + ' " ' ) ;
2023-10-22 13:27:04 +02:00
response . setContentType ( " application/octet-stream " ) ;
2017-08-02 18:55:43 +02:00
2023-10-22 23:23:41 +02:00
HttpHelper : : sendFileAndShutdown ( _saveAsSocket , resultURL . getPath ( ) , response ) ;
2017-03-18 21:36:32 +01:00
}
// Conversion is done, cleanup this fake session.
LOG_TRC ( " Removing save-as ClientSession after conversion. " ) ;
// Remove us.
2022-11-24 22:23:29 +01:00
docBroker - > removeSession ( client_from_this ( ) ) ;
2017-03-18 21:36:32 +01:00
// Now terminate.
2018-01-15 02:49:11 +01:00
docBroker - > stop ( " Finished saveas handler. " ) ;
2017-03-18 21:36:32 +01:00
}
2017-01-22 03:38:11 +01:00
return true ;
}
2018-09-13 18:16:00 +02:00
# endif
2020-03-09 09:05:30 +01:00
else if ( tokens . size ( ) = = 2 & & tokens . equals ( 0 , " statechanged: " ) )
2017-01-22 03:38:11 +01:00
{
2022-03-30 03:37:57 +02:00
StringVector stateTokens ( StringVector : : tokenize ( tokens [ 1 ] , ' = ' ) ) ;
2020-03-09 09:05:30 +01:00
if ( stateTokens . size ( ) = = 2 & & stateTokens . equals ( 0 , " .uno:ModifiedStatus " ) )
2017-01-22 03:38:11 +01:00
{
2021-07-11 21:24:46 +02:00
// Always update the modified flag in the DocBroker faithfully.
// Let it deal with the upload failure scenario and the admin console.
docBroker - > setModified ( stateTokens . equals ( 1 , " true " ) ) ;
2017-01-22 03:38:11 +01:00
}
2018-04-24 18:09:37 +02:00
else
{
// Set the initial settings per the user's request.
2018-07-13 06:18:30 +02:00
const std : : pair < std : : string , std : : string > unoStatePair = Util : : split ( tokens [ 1 ] , ' = ' ) ;
2018-04-24 18:09:37 +02:00
if ( ! docBroker - > isInitialSettingSet ( unoStatePair . first ) )
{
docBroker - > setInitialSetting ( unoStatePair . first ) ;
if ( unoStatePair . first = = " .uno:TrackChanges " )
{
if ( ( unoStatePair . second = = " true " & &
2018-11-21 09:07:52 +01:00
_wopiFileInfo & & _wopiFileInfo - > getDisableChangeTrackingRecord ( ) = = WopiStorage : : WOPIFileInfo : : TriState : : True ) | |
2018-04-24 18:09:37 +02:00
( unoStatePair . second = = " false " & &
2018-11-21 09:07:52 +01:00
_wopiFileInfo & & _wopiFileInfo - > getDisableChangeTrackingRecord ( ) = = WopiStorage : : WOPIFileInfo : : TriState : : False ) )
2018-04-24 18:09:37 +02:00
{
// Toggle the TrackChanges state.
LOG_DBG ( " Forcing " < < unoStatePair . first < < " toggle per user settings. " ) ;
forwardToChild ( " uno .uno:TrackChanges " , docBroker ) ;
}
}
else if ( unoStatePair . first = = " .uno:ShowTrackedChanges " )
{
if ( ( unoStatePair . second = = " true " & &
2018-11-21 09:07:52 +01:00
_wopiFileInfo & & _wopiFileInfo - > getDisableChangeTrackingShow ( ) = = WopiStorage : : WOPIFileInfo : : TriState : : True ) | |
2018-04-24 18:09:37 +02:00
( unoStatePair . second = = " false " & &
2018-11-21 09:07:52 +01:00
_wopiFileInfo & & _wopiFileInfo - > getDisableChangeTrackingShow ( ) = = WopiStorage : : WOPIFileInfo : : TriState : : False ) )
2018-04-24 18:09:37 +02:00
{
// Toggle the ShowTrackChanges state.
LOG_DBG ( " Forcing " < < unoStatePair . first < < " toggle per user settings. " ) ;
forwardToChild ( " uno .uno:ShowTrackedChanges " , docBroker ) ;
}
}
}
}
2023-03-22 00:36:37 +01:00
}
else if ( tokens . equals ( 0 , " textselectioncontent: " ) )
{
2019-07-04 17:47:03 +02:00
postProcessCopyPayload ( payload ) ;
2019-05-29 17:26:16 +02:00
return forwardToClient ( payload ) ;
2023-03-22 00:36:37 +01:00
}
else if ( tokens . equals ( 0 , " clipboardcontent: " ) )
{
2019-08-12 11:04:10 +02:00
# if !MOBILEAPP // Most likely nothing of this makes sense in a mobile app
2019-06-20 21:20:26 +02:00
// FIXME: Ash: we need to return different content depending
// on whether this is a download-everything, or an individual
// 'download' and/or providing our helpful / user page.
2019-05-29 17:26:16 +02:00
// for now just for remote sockets.
2022-04-05 13:50:18 +02:00
LOG_TRC ( " Got clipboard content of size " < < payload - > size ( ) < < " to send to "
< < _clipSockets . size ( ) < < " sockets in state "
< < name ( _state ) ) ;
2019-07-04 17:47:03 +02:00
postProcessCopyPayload ( payload ) ;
2020-11-15 18:03:45 +01:00
std : : size_t header ;
2019-05-29 17:26:16 +02:00
for ( header = 0 ; header < payload - > size ( ) ; )
if ( payload - > data ( ) [ header + + ] = = ' \n ' )
break ;
2019-07-04 17:47:03 +02:00
const bool empty = header > = payload - > size ( ) ;
2019-07-04 11:50:33 +02:00
// final cleanup ...
2024-02-02 14:31:19 +01:00
if ( ! empty & & ( ! _wopiFileInfo | | ! _wopiFileInfo - > getDisableCopy ( ) ) )
2021-11-18 13:08:14 +01:00
COOLWSD : : SavedClipboards - > insertClipboard (
2019-07-04 11:50:33 +02:00
_clipboardKeys , & payload - > data ( ) [ header ] , payload - > size ( ) - header ) ;
2020-06-02 09:44:08 +02:00
for ( const auto & it : _clipSockets )
2019-05-29 17:26:16 +02:00
{
2021-03-12 03:20:28 +01:00
auto socket = it . lock ( ) ;
if ( ! socket )
continue ;
2019-06-24 21:10:44 +02:00
std : : ostringstream oss ;
oss < < " HTTP/1.1 200 OK \r \n "
2019-08-23 19:57:24 +02:00
< < " Last-Modified: " < < Util : : getHttpTimeNow ( ) < < " \r \n "
2024-02-20 18:23:58 +01:00
< < " User-Agent: " < < http : : getAgentString ( ) < < " \r \n "
2019-06-24 21:10:44 +02:00
< < " Content-Length: " < < ( empty ? 0 : ( payload - > size ( ) - header ) ) < < " \r \n "
< < " Content-Type: application/octet-stream \r \n "
< < " X-Content-Type-Options: nosniff \r \n "
2022-09-08 13:28:28 +02:00
< < " Connection: close \r \n "
2019-06-24 21:10:44 +02:00
< < " \r \n " ;
2020-04-19 20:46:01 +02:00
2019-06-24 21:10:44 +02:00
if ( ! empty )
2019-05-29 17:26:16 +02:00
{
oss . write ( & payload - > data ( ) [ header ] , payload - > size ( ) - header ) ;
2020-11-15 18:03:45 +01:00
socket - > setSocketBufferSize (
std : : min ( payload - > size ( ) + 256 , std : : size_t ( Socket : : MaximumSendBufferSize ) ) ) ;
2019-05-29 17:26:16 +02:00
}
2020-04-19 20:46:01 +02:00
2019-06-24 21:10:44 +02:00
socket - > send ( oss . str ( ) ) ;
socket - > shutdown ( ) ;
LOG_INF ( " Queued " < < ( empty ? " empty " : " clipboard " ) < < " response for send. " ) ;
2019-05-29 17:26:16 +02:00
}
2019-08-12 11:04:10 +02:00
# endif
2019-05-29 17:26:16 +02:00
_clipSockets . clear ( ) ;
2019-06-22 00:56:45 +02:00
return true ;
2023-03-22 00:36:37 +01:00
}
else if ( tokens . equals ( 0 , " disconnected: " ) )
{
2019-07-04 11:50:33 +02:00
LOG_INF ( " End of disconnection handshake for " < < getId ( ) ) ;
2022-11-24 13:56:12 +01:00
docBroker - > finalRemoveSession ( client_from_this ( ) ) ;
2019-07-04 11:50:33 +02:00
return true ;
2017-01-22 03:38:11 +01:00
}
2023-02-12 01:04:42 +01:00
else if ( tokens . equals ( 0 , " graphicselection: " ) | | tokens . equals ( 0 , " graphicviewselection: " ) )
2022-08-27 18:32:42 +02:00
{
2023-03-09 15:10:39 +01:00
if ( _thumbnailSession )
{
int x , y ;
if ( stringToInteger ( tokens [ 1 ] , x ) & &
stringToInteger ( tokens [ 2 ] , y ) )
{
std : : ostringstream renderThumbnailCmd ;
renderThumbnailCmd < < " getthumbnail x= " < < x < < " y= " < < y ;
docBroker - > forwardToChild ( client_from_this ( ) , renderThumbnailCmd . str ( ) ) ;
}
}
2023-02-12 01:04:42 +01:00
if ( payload - > find ( " url " , 3 ) > = 0 )
2022-10-25 13:28:55 +02:00
{
2023-02-12 01:04:42 +01:00
std : : string json ( payload - > data ( ) . data ( ) , payload - > size ( ) ) ;
const auto it = json . find ( ' { ' ) ;
const std : : string prefix = json . substr ( 0 , it ) ;
json . erase ( 0 , it ) ; // Remove the prefix to parse the purse JSON part.
2022-10-25 13:28:55 +02:00
2023-02-12 01:04:42 +01:00
Poco : : JSON : : Object : : Ptr object ;
if ( JsonUtil : : parseJSON ( json , object ) )
{
const std : : string url = JsonUtil : : getJSONValue < std : : string > ( object , " url " ) ;
if ( ! url . empty ( ) )
{
const std : : string id = JsonUtil : : getJSONValue < std : : string > ( object , " id " ) ;
if ( ! id . empty ( ) )
{
docBroker - > addEmbeddedMedia (
id , json ) ; // Capture the original message with internal URL.
const std : : string mediaUrl = Util : : encodeURIComponent (
createPublicURI ( " media " , id , /*encode=*/ false ) , " & " ) ;
object - > set ( " url " , mediaUrl ) ; // Replace the url with the public one.
object - > set ( " mimeType " , " video/mp4 " ) ; //FIXME: get this from the source json
std : : ostringstream mediaStr ;
object - > stringify ( mediaStr ) ;
const std : : string msg = prefix + mediaStr . str ( ) ;
forwardToClient ( std : : make_shared < Message > ( msg , Message : : Dir : : Out ) ) ;
return true ;
}
else
{
LOG_ERR ( " Invalid embeddedmedia json without id: " < < json ) ;
}
}
2022-10-25 13:28:55 +02:00
}
}
2023-02-12 01:04:42 +01:00
// Non-Media graphic selsection.
forwardToClient ( payload ) ;
2022-08-27 18:32:42 +02:00
return true ;
}
2021-10-18 16:41:12 +02:00
else if ( tokens . equals ( 0 , " formfieldbutton: " ) ) {
2021-02-10 12:13:16 +01:00
// Do not send redundant messages
if ( _lastSentFormFielButtonMessage = = firstLine )
return true ;
_lastSentFormFielButtonMessage = firstLine ;
}
2023-02-27 13:43:25 +01:00
else if ( tokens . equals ( 0 , " canonicalidchange: " ) ) {
int viewId , canonicalId ;
if ( getTokenInteger ( tokens [ 1 ] , " viewid " , viewId ) & &
getTokenInteger ( tokens [ 2 ] , " canonicalid " , canonicalId ) )
{
_canonicalViewId = canonicalId ;
}
}
2023-12-01 11:24:16 +01:00
# if ENABLE_FEATURE_LOCK || ENABLE_FEATURE_RESTRICTION
2024-01-10 10:34:48 +01:00
else if ( tokens . equals ( 0 , " status: " ) & & ! isViewLoaded ( ) )
2023-12-01 11:24:16 +01:00
{
std : : ostringstream blockingCommandStatus ;
blockingCommandStatus < < " blockingcommandstatus isRestrictedUser= "
< < ( CommandControl : : RestrictionManager : : isRestrictedUser ( ) ? " true "
: " false " )
< < " isLockedUser= "
< < ( CommandControl : : LockManager : : isLockedUser ( ) ? " true " : " false " ) ;
docBroker - > forwardToChild ( client_from_this ( ) , blockingCommandStatus . str ( ) ) ;
}
# endif
2018-11-13 09:04:19 +01:00
if ( ! isDocPasswordProtected ( ) )
2017-01-22 03:38:11 +01:00
{
2021-10-18 16:41:12 +02:00
if ( tokens . equals ( 0 , " tile: " ) )
2017-01-22 03:38:11 +01:00
{
assert ( false & & " Tile traffic should go through the DocumentBroker-LoKit WS. " ) ;
}
2021-10-18 16:41:12 +02:00
else if ( tokens . equals ( 0 , " jsdialog: " ) & & _state = = ClientSession : : SessionState : : LOADING )
2021-02-26 16:01:39 +01:00
{
docBroker - > setInteractive ( true ) ;
}
2021-10-18 16:41:12 +02:00
else if ( tokens . equals ( 0 , " status: " ) )
2017-01-22 03:38:11 +01:00
{
2019-07-04 11:50:33 +02:00
setState ( ClientSession : : SessionState : : LIVE ) ;
2021-02-26 16:01:39 +01:00
docBroker - > setInteractive ( false ) ;
2017-01-22 03:38:11 +01:00
docBroker - > setLoaded ( ) ;
2019-11-12 10:50:33 +01:00
2022-04-05 04:36:05 +02:00
if ( UnitWSD : : isUnitTesting ( ) )
{
2022-11-27 00:47:52 +01:00
UnitWSD : : get ( ) . onDocBrokerViewLoaded ( docBroker - > getDocKey ( ) , client_from_this ( ) ) ;
2022-04-05 04:36:05 +02:00
}
2019-11-12 10:50:33 +01:00
# if !MOBILEAPP
Admin : : instance ( ) . setViewLoadDuration ( docBroker - > getDocKey ( ) , getId ( ) , std : : chrono : : duration_cast < std : : chrono : : milliseconds > ( std : : chrono : : steady_clock : : now ( ) - _viewLoadStart ) ) ;
# endif
2023-02-24 09:14:03 +01:00
// position cursor for thumbnail rendering
if ( _thumbnailSession )
{
//check whether we have a target!
std : : ostringstream cmd ;
cmd < < " { " ;
cmd < < " \" Name \" : "
" { "
" \" type \" : \" string \" , "
" \" value \" : \" URL \" "
" }, "
" \" URL \" : "
" { "
" \" type \" : \" string \" , "
" \" value \" : \" # " ;
cmd < < getThumbnailTarget ( ) ;
cmd < < " \" }} " ;
const std : : string renderThumbnailCmd = " uno .uno:OpenHyperLink " + cmd . str ( ) ;
docBroker - > forwardToChild ( client_from_this ( ) , renderThumbnailCmd ) ;
}
2019-05-22 05:33:26 +02:00
// Wopi post load actions
if ( _wopiFileInfo & & ! _wopiFileInfo - > getTemplateSource ( ) . empty ( ) )
{
2021-05-27 04:08:57 +02:00
LOG_DBG ( " Uploading template [ " < < _wopiFileInfo - > getTemplateSource ( )
< < " ] to storage after loading. " ) ;
2022-11-25 14:35:46 +01:00
docBroker - > uploadAfterLoadingTemplate ( client_from_this ( ) ) ;
2019-05-22 05:33:26 +02:00
}
2017-01-22 03:38:11 +01:00
2018-06-16 14:22:01 +02:00
for ( auto & token : tokens )
2018-05-31 20:20:09 +02:00
{
2018-06-16 14:22:01 +02:00
// Need to get the initial part id from status message
int part = - 1 ;
2020-02-28 14:51:22 +01:00
if ( getTokenInteger ( tokens . getParam ( token ) , " current " , part ) )
2018-06-16 14:22:01 +02:00
{
_clientSelectedPart = part ;
}
2022-08-30 07:27:44 +02:00
int mode = 0 ;
if ( getTokenInteger ( tokens . getParam ( token ) , " mode " , mode ) )
_clientSelectedMode = mode ;
2018-06-16 14:22:01 +02:00
// Get document type too
std : : string docType ;
2020-02-28 14:51:22 +01:00
if ( getTokenString ( tokens . getParam ( token ) , " type " , docType ) )
2018-06-16 14:22:01 +02:00
{
2018-06-19 16:15:37 +02:00
_isTextDocument = docType . find ( " text " ) ! = std : : string : : npos ;
2018-06-16 14:22:01 +02:00
}
2019-05-29 17:26:16 +02:00
// Store our Kit ViewId
int viewId = - 1 ;
2020-02-28 14:51:22 +01:00
if ( getTokenInteger ( tokens . getParam ( token ) , " viewid " , viewId ) )
2019-05-29 17:26:16 +02:00
_kitViewId = viewId ;
2018-05-31 20:20:09 +02:00
}
2017-01-22 03:38:11 +01:00
// Forward the status response to the client.
return forwardToClient ( payload ) ;
}
2021-10-18 16:41:12 +02:00
else if ( tokens . equals ( 0 , " commandvalues: " ) )
2017-01-22 03:38:11 +01:00
{
2022-06-06 00:37:03 +02:00
const std : : string stringJSON = payload - > jsonString ( ) ;
if ( ! stringJSON . empty ( ) )
2017-01-22 03:38:11 +01:00
{
2022-01-06 17:26:15 +01:00
try
{
Poco : : JSON : : Parser parser ;
const Poco : : Dynamic : : Var result = parser . parse ( stringJSON ) ;
const auto & object = result . extract < Poco : : JSON : : Object : : Ptr > ( ) ;
const std : : string commandName = object - > has ( " commandName " ) ? object - > get ( " commandName " ) . toString ( ) : " " ;
if ( commandName = = " .uno:CharFontName " | |
commandName = = " .uno:StyleApply " )
{
// other commands should not be cached
2022-06-06 00:37:03 +02:00
docBroker - > tileCache ( ) . saveTextStream ( TileCache : : StreamType : : CmdValues ,
commandName , payload - > data ( ) ) ;
2022-01-06 17:26:15 +01:00
}
}
catch ( const std : : exception & exception )
2017-01-22 03:38:11 +01:00
{
2022-01-06 17:26:15 +01:00
LOG_ERR ( " commandvalues parsing failure: " < < exception . what ( ) ) ;
2017-01-22 03:38:11 +01:00
}
}
}
2021-10-18 16:41:12 +02:00
else if ( tokens . equals ( 0 , " invalidatetiles: " ) )
2017-01-22 03:38:11 +01:00
{
2022-06-06 00:37:03 +02:00
assert ( firstLine . size ( ) = = payload - > size ( ) & &
" Unexpected multiline data in invalidatetiles " ) ;
2018-08-24 14:01:44 +02:00
// First forward invalidation
bool ret = forwardToClient ( payload ) ;
2018-05-31 20:20:09 +02:00
handleTileInvalidation ( firstLine , docBroker ) ;
2018-08-24 14:01:44 +02:00
return ret ;
2017-01-22 03:38:11 +01:00
}
2023-05-17 09:44:26 +02:00
else if ( tokens . equals ( 0 , " statechanged: " ) )
{
if ( _thumbnailSession )
{
2023-05-24 21:40:19 +02:00
// fallback in case we setup target at first character in the text document,
// or not existing target and we will not enter invalidatecursor second time
std : : ostringstream renderThumbnailCmd ;
auto position = getThumbnailPosition ( ) ;
renderThumbnailCmd < < " getthumbnail x= " < < position . first < < " y= " < < position . second ;
docBroker - > forwardToChild ( client_from_this ( ) , renderThumbnailCmd . str ( ) ) ;
2023-05-17 09:44:26 +02:00
}
}
2021-10-18 16:41:12 +02:00
else if ( tokens . equals ( 0 , " invalidatecursor: " ) )
2017-01-22 03:38:11 +01:00
{
2022-06-06 00:37:03 +02:00
assert ( firstLine . size ( ) = = payload - > size ( ) & &
" Unexpected multiline data in invalidatecursor " ) ;
2018-04-12 20:28:54 +02:00
2022-06-06 00:37:03 +02:00
const std : : string stringJSON = payload - > jsonString ( ) ;
2018-04-12 20:28:54 +02:00
Poco : : JSON : : Parser parser ;
2022-01-06 17:26:15 +01:00
try
2017-01-22 03:38:11 +01:00
{
2022-01-06 17:26:15 +01:00
const Poco : : Dynamic : : Var result = parser . parse ( stringJSON ) ;
const auto & object = result . extract < Poco : : JSON : : Object : : Ptr > ( ) ;
const std : : string rectangle = object - > get ( " rectangle " ) . toString ( ) ;
2022-03-30 03:37:57 +02:00
StringVector rectangleTokens ( StringVector : : tokenize ( rectangle , ' , ' ) ) ;
2022-01-06 17:26:15 +01:00
int x = 0 , y = 0 , w = 0 , h = 0 ;
if ( rectangleTokens . size ( ) > 2 & &
stringToInteger ( rectangleTokens [ 0 ] , x ) & &
stringToInteger ( rectangleTokens [ 1 ] , y ) )
2017-01-22 03:38:11 +01:00
{
2022-01-06 17:26:15 +01:00
if ( rectangleTokens . size ( ) > 3 )
{
stringToInteger ( rectangleTokens [ 2 ] , w ) ;
stringToInteger ( rectangleTokens [ 3 ] , h ) ;
}
2017-01-26 02:55:39 +01:00
2022-01-06 17:26:15 +01:00
docBroker - > invalidateCursor ( x , y , w , h ) ;
2023-02-24 09:14:03 +01:00
// session used for thumbnailing and target already was set
if ( _thumbnailSession )
{
2023-05-17 09:44:26 +02:00
setThumbnailPosition ( std : : make_pair ( x , y ) ) ;
2023-02-24 09:14:03 +01:00
bool cursorAlreadyAtTargetPosition = getThumbnailTarget ( ) . empty ( ) ;
if ( cursorAlreadyAtTargetPosition )
{
std : : ostringstream renderThumbnailCmd ;
renderThumbnailCmd < < " getthumbnail x= " < < x < < " y= " < < y ;
docBroker - > forwardToChild ( client_from_this ( ) , renderThumbnailCmd . str ( ) ) ;
}
else
{
// this is initial cursor position message
// wait for second invalidatecursor message
// reset target so we will proceed next time
setThumbnailTarget ( std : : string ( ) ) ;
}
}
2022-01-06 17:26:15 +01:00
}
else
{
LOG_ERR ( " Unable to parse " < < firstLine ) ;
}
2017-01-22 03:38:11 +01:00
}
2022-01-06 17:26:15 +01:00
catch ( const std : : exception & exception )
2017-01-22 03:38:11 +01:00
{
2022-01-06 17:26:15 +01:00
LOG_ERR ( " invalidatecursor parsing failure: " < < exception . what ( ) ) ;
2017-01-22 03:38:11 +01:00
}
}
2021-10-18 16:41:12 +02:00
else if ( tokens . equals ( 0 , " renderfont: " ) )
2017-01-22 03:38:11 +01:00
{
std : : string font , text ;
2017-01-23 05:19:04 +01:00
if ( tokens . size ( ) < 3 | |
2017-01-22 03:38:11 +01:00
! getTokenString ( tokens [ 1 ] , " font " , font ) )
{
LOG_ERR ( " Bad syntax for: " < < firstLine ) ;
return false ;
}
getTokenString ( tokens [ 2 ] , " char " , text ) ;
2022-06-06 00:37:03 +02:00
assert ( firstLine . size ( ) < payload - > size ( ) & & " Missing multiline data in renderfont " ) ;
docBroker - > tileCache ( ) . saveStream ( TileCache : : StreamType : : Font , font + text ,
payload - > data ( ) . data ( ) + firstLine . size ( ) + 1 ,
payload - > data ( ) . size ( ) - firstLine . size ( ) - 1 ) ;
2017-01-22 03:38:11 +01:00
return forwardToClient ( payload ) ;
}
2023-02-22 11:11:35 +01:00
else if ( tokens . equals ( 0 , " extractedlinktargets: " ) )
{
LOG_TRC ( " Sending extracted link targets response. " ) ;
2023-10-17 12:05:15 +02:00
if ( ! _saveAsSocket )
LOG_ERR ( " Error in extractedlinktargets: not in isConvertTo mode " ) ;
else
{
const std : : string stringJSON = payload - > jsonString ( ) ;
2023-02-22 11:11:35 +01:00
2023-10-17 12:05:15 +02:00
http : : Response httpResponse ( http : : StatusCode : : OK ) ;
2024-05-08 12:04:54 +02:00
FileServerRequestHandler : : hstsHeaders ( httpResponse ) ;
2023-10-17 12:05:15 +02:00
httpResponse . set ( " Last-Modified " , Util : : getHttpTimeNow ( ) ) ;
httpResponse . set ( " X-Content-Type-Options " , " nosniff " ) ;
httpResponse . setBody ( stringJSON , " application/json " ) ;
_saveAsSocket - > sendAndShutdown ( httpResponse ) ;
}
2023-02-22 11:11:35 +01:00
// Now terminate.
docBroker - > closeDocument ( " extractedlinktargets " ) ;
return true ;
}
2023-02-23 18:30:25 +01:00
else if ( tokens . equals ( 0 , " sendthumbnail: " ) )
{
LOG_TRC ( " Sending get-thumbnail response. " ) ;
2023-10-17 12:05:15 +02:00
if ( ! _saveAsSocket )
LOG_ERR ( " Error in sendthumbnail: not in isConvertTo mode " ) ;
else
2023-02-24 09:14:03 +01:00
{
2023-10-17 12:05:15 +02:00
bool error = false ;
2023-02-23 18:30:25 +01:00
2023-10-17 12:05:15 +02:00
if ( firstLine . find ( " error " ) ! = std : : string : : npos )
error = true ;
2023-02-23 18:30:25 +01:00
2023-10-17 12:05:15 +02:00
if ( ! error )
{
int firstLineSize = firstLine . size ( ) + 1 ;
std : : string thumbnail ( payload - > data ( ) . data ( ) + firstLineSize , payload - > data ( ) . size ( ) - firstLineSize ) ;
http : : Response httpResponse ( http : : StatusCode : : OK ) ;
2024-05-08 12:04:54 +02:00
FileServerRequestHandler : : hstsHeaders ( httpResponse ) ;
2023-10-17 12:05:15 +02:00
httpResponse . set ( " Last-Modified " , Util : : getHttpTimeNow ( ) ) ;
httpResponse . set ( " X-Content-Type-Options " , " nosniff " ) ;
2024-01-27 21:16:16 +01:00
httpResponse . setBody ( std : : move ( thumbnail ) , " image/png " ) ;
2023-10-17 12:05:15 +02:00
_saveAsSocket - > sendAndShutdown ( httpResponse ) ;
}
if ( error )
{
http : : Response httpResponse ( http : : StatusCode : : InternalServerError ) ;
httpResponse . set ( " Content-Length " , " 0 " ) ;
_saveAsSocket - > sendAndShutdown ( httpResponse ) ;
}
2023-02-24 09:14:03 +01:00
}
2023-02-23 18:30:25 +01:00
2023-02-24 09:14:03 +01:00
docBroker - > closeDocument ( " thumbnailgenerated " ) ;
2023-02-23 18:30:25 +01:00
}
2017-01-22 03:38:11 +01:00
}
else
{
LOG_INF ( " Ignoring notification on password protected document: " < < firstLine ) ;
}
// Forward everything else.
return forwardToClient ( payload ) ;
}
bool ClientSession : : forwardToClient ( const std : : shared_ptr < Message > & payload )
{
if ( isCloseFrame ( ) )
{
2022-06-04 15:16:04 +02:00
LOG_TRC ( " peer began the closing handshake. Dropping forward message [ " < < payload - > abbr ( )
< < ' ] ' ) ;
2017-01-22 03:38:11 +01:00
return true ;
}
enqueueSendMessage ( payload ) ;
return true ;
2017-01-22 02:31:22 +01:00
}
2018-09-26 22:15:37 +02:00
void ClientSession : : enqueueSendMessage ( const std : : shared_ptr < Message > & data )
{
2022-04-04 02:08:55 +02:00
if ( isCloseFrame ( ) )
{
2022-06-04 15:16:04 +02:00
LOG_TRC ( " Connection closed, dropping message " < < data - > id ( ) ) ;
2022-04-04 02:08:55 +02:00
return ;
}
2018-09-26 22:15:37 +02:00
const std : : shared_ptr < DocumentBroker > docBroker = _docBroker . lock ( ) ;
2020-08-07 18:36:56 +02:00
LOG_CHECK_RET ( docBroker & & " Null DocumentBroker instance " , ) ;
2023-05-17 12:34:26 +02:00
docBroker - > ASSERT_CORRECT_THREAD ( ) ;
2018-09-26 22:15:37 +02:00
2018-10-01 18:11:25 +02:00
std : : unique_ptr < TileDesc > tile ;
2023-12-07 20:42:55 +01:00
if ( data - > firstTokenMatches ( " tile: " ) | |
data - > firstTokenMatches ( " delta: " ) )
2018-09-26 22:15:37 +02:00
{
2023-12-05 10:37:23 +01:00
// Avoid sending tile or delta if it has the same wireID as the
// previously sent tile
2023-08-01 13:21:20 +02:00
tile = std : : make_unique < TileDesc > ( TileDesc : : parse ( data - > firstLine ( ) ) ) ;
2018-09-26 22:15:37 +02:00
}
2022-06-04 15:16:04 +02:00
LOG_TRC ( " Enqueueing client message " < < data - > id ( ) ) ;
2020-11-15 18:03:45 +01:00
std : : size_t sizeBefore = _senderQueue . size ( ) ;
std : : size_t newSize = _senderQueue . enqueue ( data ) ;
2018-09-26 22:15:37 +02:00
// Track sent tile
2023-12-07 20:42:55 +01:00
if ( tile & & sizeBefore ! = newSize )
2023-12-05 10:37:23 +01:00
addTileOnFly ( tile - > getWireId ( ) ) ;
2018-09-26 22:15:37 +02:00
}
2023-12-05 10:37:23 +01:00
void ClientSession : : addTileOnFly ( TileWireId wireId )
2018-07-10 14:10:28 +02:00
{
2023-12-05 10:37:23 +01:00
_tilesOnFly . emplace_back ( wireId , std : : chrono : : steady_clock : : now ( ) ) ;
2018-07-10 14:05:36 +02:00
}
2018-06-13 15:04:09 +02:00
2023-12-04 10:12:15 +01:00
size_t ClientSession : : getTilesOnFlyUpperLimit ( ) const
{
// How many tiles we have on the visible area, set the upper limit accordingly
Util : : Rectangle normalizedVisArea = getNormalizedVisibleArea ( ) ;
float tilesOnFlyUpperLimit = 0 ;
if ( normalizedVisArea . hasSurface ( ) & & getTileWidthInTwips ( ) ! = 0 & & getTileHeightInTwips ( ) ! = 0 )
{
2023-12-20 16:12:15 +01:00
const int tilesFitOnWidth = ( normalizedVisArea . getRight ( ) / getTileWidthInTwips ( ) ) -
( normalizedVisArea . getLeft ( ) / getTileWidthInTwips ( ) ) + 1 ;
const int tilesFitOnHeight = ( normalizedVisArea . getBottom ( ) / getTileHeightInTwips ( ) ) -
( normalizedVisArea . getTop ( ) / getTileHeightInTwips ( ) ) + 1 ;
2023-12-04 10:12:15 +01:00
const int tilesInVisArea = tilesFitOnWidth * tilesFitOnHeight ;
tilesOnFlyUpperLimit = std : : max ( TILES_ON_FLY_MIN_UPPER_LIMIT , tilesInVisArea * 1.1f ) ;
}
else
{
tilesOnFlyUpperLimit = 200 ; // Have a big number here to get all tiles requested by file opening
}
return tilesOnFlyUpperLimit ;
}
2023-12-07 22:59:21 +01:00
void ClientSession : : removeOutdatedTilesOnFly ( const std : : chrono : : steady_clock : : time_point & now )
2018-08-23 12:46:49 +02:00
{
2024-01-05 12:17:41 +01:00
size_t dropped = 0 ;
const auto highTimeoutMs = std : : chrono : : milliseconds ( TILE_ROUNDTRIP_TIMEOUT_MS ) ;
const auto lowTimeoutMs = std : : chrono : : milliseconds ( ( int ) ( 0.9 * TILE_ROUNDTRIP_TIMEOUT_MS ) ) ;
2018-08-23 13:27:47 +02:00
// Check only the beginning of the list, tiles are ordered by timestamp
2023-12-05 09:41:59 +01:00
while ( ! _tilesOnFly . empty ( ) )
2018-08-23 12:46:49 +02:00
{
2018-08-23 13:27:47 +02:00
auto tileIter = _tilesOnFly . begin ( ) ;
2023-12-07 22:59:21 +01:00
const auto elapsedTimeMs = std : : chrono : : duration_cast <
std : : chrono : : milliseconds > ( now - tileIter - > second ) ;
2024-01-05 12:17:41 +01:00
if ( elapsedTimeMs > highTimeoutMs | |
// once we start dropping - drop lots in a similar range of time
( dropped > 0 & & elapsedTimeMs > lowTimeoutMs ) )
{
LOG_TRC ( " Tracker tileID " < < tileIter - > first < < " was dropped because of time out ( "
< < elapsedTimeMs
< < " ). Tileprocessed message did not arrive in time. " ) ;
dropped + + ;
2018-08-23 12:46:49 +02:00
_tilesOnFly . erase ( tileIter ) ;
}
2018-08-23 13:27:47 +02:00
else
2023-12-05 09:41:59 +01:00
break ;
2018-08-23 12:46:49 +02:00
}
2024-01-05 12:17:41 +01:00
if ( dropped > 0 )
LOG_WRN ( " client not consuming tiles; stalled for " < < ( TILE_ROUNDTRIP_TIMEOUT_MS / 1000 ) < < " seconds: removed tracking for " < < dropped < < " on the fly tiles " ) ;
2018-08-23 12:46:49 +02:00
}
2018-08-30 17:40:42 +02:00
Util : : Rectangle ClientSession : : getNormalizedVisibleArea ( ) const
{
Util : : Rectangle normalizedVisArea ;
2018-11-14 09:07:47 +01:00
normalizedVisArea . setLeft ( std : : max ( _clientVisibleArea . getLeft ( ) , 0 ) ) ;
normalizedVisArea . setTop ( std : : max ( _clientVisibleArea . getTop ( ) , 0 ) ) ;
normalizedVisArea . setRight ( _clientVisibleArea . getRight ( ) ) ;
normalizedVisArea . setBottom ( _clientVisibleArea . getBottom ( ) ) ;
2018-08-30 17:40:42 +02:00
return normalizedVisArea ;
}
2017-03-15 03:19:51 +01:00
void ClientSession : : onDisconnect ( )
{
2022-06-04 15:16:04 +02:00
LOG_INF ( " Disconnected, current global number of connections (inclusive): "
< < COOLWSD : : NumConnections ) ;
2017-03-15 03:19:51 +01:00
2018-02-07 10:17:59 +01:00
const std : : shared_ptr < DocumentBroker > docBroker = getDocumentBroker ( ) ;
2017-03-15 03:19:51 +01:00
LOG_CHECK_RET ( docBroker & & " Null DocumentBroker instance " , ) ;
2023-05-17 12:34:26 +02:00
docBroker - > ASSERT_CORRECT_THREAD ( ) ;
2018-02-07 10:17:59 +01:00
const std : : string docKey = docBroker - > getDocKey ( ) ;
2017-03-15 03:19:51 +01:00
2020-03-18 09:20:31 +01:00
// Keep self alive, so that our own dtor runs only at the end of this function. Without this,
// removeSession() may destroy us and then we can't call our own member functions anymore.
std : : shared_ptr < ClientSession > session = client_from_this ( ) ;
2017-03-15 03:19:51 +01:00
try
{
// Connection terminated. Destroy session.
2022-06-04 15:16:04 +02:00
LOG_DBG ( " on docKey [ " < < docKey < < " ] terminated. Cleaning up " ) ;
2017-03-15 03:19:51 +01:00
2022-11-24 22:23:29 +01:00
docBroker - > removeSession ( session ) ;
2017-03-15 03:19:51 +01:00
}
catch ( const UnauthorizedRequestException & exc )
{
2022-06-04 15:16:04 +02:00
LOG_ERR ( " Error in client request handler: " < < exc . toString ( ) ) ;
2017-03-15 03:19:51 +01:00
const std : : string status = " error: cmd=internal kind=unauthorized " ;
2022-06-04 15:16:04 +02:00
LOG_TRC ( " Sending to Client [ " < < status < < ' ] ' ) ;
2017-03-30 02:03:01 +02:00
sendMessage ( status ) ;
2021-06-30 16:37:31 +02:00
// We are disconnecting, no need to close the socket here.
2017-03-15 03:19:51 +01:00
}
catch ( const std : : exception & exc )
{
2022-06-04 15:16:04 +02:00
LOG_ERR ( " Error in client request handler: " < < exc . what ( ) ) ;
2017-03-15 03:19:51 +01:00
}
try
{
if ( isCloseFrame ( ) )
{
2022-06-04 15:16:04 +02:00
LOG_TRC ( " Normal close handshake. " ) ;
2017-03-15 03:19:51 +01:00
// Client initiated close handshake
// respond with close frame
2020-03-05 19:42:00 +01:00
shutdownNormal ( ) ;
2017-03-15 03:19:51 +01:00
}
2019-08-12 09:03:16 +02:00
else if ( ! SigUtil : : getShutdownRequestFlag ( ) )
2017-03-15 03:19:51 +01:00
{
// something wrong, with internal exceptions
2022-06-04 15:16:04 +02:00
LOG_TRC ( " Abnormal close handshake. " ) ;
2017-03-15 03:19:51 +01:00
closeFrame ( ) ;
2020-03-05 19:42:00 +01:00
shutdownGoingAway ( ) ;
2017-03-15 03:19:51 +01:00
}
else
{
2022-06-04 15:16:04 +02:00
LOG_TRC ( " Server recycling. " ) ;
2017-04-10 01:41:29 +02:00
closeFrame ( ) ;
2020-03-05 19:42:00 +01:00
shutdownGoingAway ( ) ;
2017-03-15 03:19:51 +01:00
}
}
catch ( const std : : exception & exc )
{
2022-06-04 15:16:04 +02:00
LOG_ERR ( " Exception while closing socket for docKey [ " < < docKey < < " ]: " < < exc . what ( ) ) ;
2017-03-15 03:19:51 +01:00
}
}
2017-03-18 15:59:09 +01:00
void ClientSession : : dumpState ( std : : ostream & os )
{
Session : : dumpState ( os ) ;
2023-12-04 10:12:15 +01:00
const std : : shared_ptr < DocumentBroker > docBroker = _docBroker . lock ( ) ;
2017-03-18 15:59:09 +01:00
2022-04-02 01:50:56 +02:00
os < < " \t \t isLive: " < < isLive ( )
2022-04-02 23:19:50 +02:00
< < " \n \t \t isViewLoaded: " < < isViewLoaded ( )
2019-10-16 15:12:02 +02:00
< < " \n \t \t isDocumentOwner: " < < isDocumentOwner ( )
2022-04-05 13:50:18 +02:00
< < " \n \t \t state: " < < name ( _state )
2019-05-29 17:26:16 +02:00
< < " \n \t \t keyEvents: " < < _keyEvents
// << "\n\t\tvisibleArea: " << _clientVisibleArea
< < " \n \t \t clientSelectedPart: " < < _clientSelectedPart
2020-05-24 15:10:18 +02:00
< < " \n \t \t tile size Pixel: " < < _tileWidthPixel < < ' x ' < < _tileHeightPixel
< < " \n \t \t tile size Twips: " < < _tileWidthTwips < < ' x ' < < _tileHeightTwips
2019-05-29 17:26:16 +02:00
< < " \n \t \t kit ViewId: " < < _kitViewId
2020-05-06 18:02:51 +02:00
< < " \n \t \t our URL (un-trusted): " < < _serverURL . getSubURLForEndpoint ( " " )
2019-05-29 17:26:16 +02:00
< < " \n \t \t isTextDocument: " < < _isTextDocument
2019-06-21 13:35:17 +02:00
< < " \n \t \t clipboardKeys[0]: " < < _clipboardKeys [ 0 ]
< < " \n \t \t clipboardKeys[1]: " < < _clipboardKeys [ 1 ]
2020-05-13 00:52:25 +02:00
< < " \n \t \t clip sockets: " < < _clipSockets . size ( )
2022-08-30 07:27:44 +02:00
< < " \n \t \t proxy access:: " < < _proxyAccess
2023-12-04 10:12:15 +01:00
< < " \n \t \t clientSelectedMode: " < < _clientSelectedMode
< < " \n \t \t requestedTiles: " < < getRequestedTiles ( ) . size ( )
< < " \n \t \t beingRendered: " < < ( ! docBroker ? - 1 : docBroker - > tileCache ( ) . countTilesBeingRenderedForSession ( client_from_this ( ) , std : : chrono : : steady_clock : : now ( ) ) ) ;
2017-09-11 19:59:38 +02:00
2020-03-06 18:43:46 +01:00
if ( _protocol )
2017-09-11 19:59:38 +02:00
{
2020-05-25 10:57:29 +02:00
uint64_t sent = 0 , recv = 0 ;
2020-03-06 18:43:46 +01:00
_protocol - > getIOStats ( sent , recv ) ;
2024-01-05 12:37:05 +01:00
os < < " \n \t \t sent/keystroke: " < < ( double ) sent / _keyEvents < < " bytes " ;
}
os < < " \n \t \t onFlyUpperLimit: " < < getTilesOnFlyUpperLimit ( ) ;
os < < " \n \t \t onFlyCount: " < < getTilesOnFlyCount ( ) ;
if ( _tilesOnFly . size ( ) > 0 )
os < < " between wid: " < < _tilesOnFly . front ( ) . first < < " as of " < <
std : : chrono : : duration_cast < std : : chrono : : milliseconds > (
std : : chrono : : steady_clock : : now ( ) - _tilesOnFly . front ( ) . second ) < < " ms "
< < " and wid: " < < _tilesOnFly . back ( ) . first < < " as of " < <
std : : chrono : : duration_cast < std : : chrono : : milliseconds > (
std : : chrono : : steady_clock : : now ( ) - _tilesOnFly . back ( ) . second ) < < " ms " ;
2023-12-05 10:37:23 +01:00
2020-05-24 15:10:18 +02:00
os < < ' \n ' ;
2017-04-05 18:59:29 +02:00
_senderQueue . dumpState ( os ) ;
2017-03-18 15:59:09 +01:00
2023-12-05 10:37:23 +01:00
// FIXME: need to dump other bits ...
2017-09-11 19:59:38 +02:00
}
2017-03-18 15:59:09 +01:00
2020-05-13 00:52:25 +02:00
const std : : string & ClientSession : : getOrCreateProxyAccess ( )
{
if ( _proxyAccess . size ( ) < = 0 )
2024-02-05 12:50:28 +01:00
_proxyAccess = Util : : rng : : getHexString (
2020-05-13 00:52:25 +02:00
ProxyAccessTokenLengthBytes ) ;
return _proxyAccess ;
}
2018-05-31 20:20:09 +02:00
void ClientSession : : handleTileInvalidation ( const std : : string & message ,
const std : : shared_ptr < DocumentBroker > & docBroker )
{
2020-01-02 22:11:54 +01:00
docBroker - > invalidateTiles ( message , getCanonicalViewId ( ) ) ;
2018-05-31 20:20:09 +02:00
// Skip requesting new tiles if we don't have client visible area data yet.
if ( ! _clientVisibleArea . hasSurface ( ) | |
_tileWidthPixel = = 0 | | _tileHeightPixel = = 0 | |
_tileWidthTwips = = 0 | | _tileHeightTwips = = 0 | |
2018-06-19 16:15:37 +02:00
( _clientSelectedPart = = - 1 & & ! _isTextDocument ) )
2018-05-31 20:20:09 +02:00
{
return ;
}
2021-07-09 15:45:54 +02:00
// While saving / shutting down we can get big invalidatiions: ignore them
if ( isCloseFrame ( ) ) {
LOG_TRC ( " Session [ " < < getId ( ) < < " ] ignoring invalidation during close: ' " < < message ) ;
return ;
}
2023-07-06 13:08:59 +02:00
int part = 0 , mode = 0 ;
TileWireId wireId = 0 ;
Util : : Rectangle invalidateRect = TileCache : : parseInvalidateMsg ( message , part , mode , wireId ) ;
2018-05-31 20:20:09 +02:00
2020-07-08 03:40:13 +02:00
constexpr SplitPaneName panes [ 4 ] = {
TOPLEFT_PANE ,
TOPRIGHT_PANE ,
BOTTOMLEFT_PANE ,
BOTTOMRIGHT_PANE
} ;
Util : : Rectangle paneRects [ 4 ] ;
int numPanes = 0 ;
for ( int i = 0 ; i < 4 ; + + i )
{
if ( ! isSplitPane ( panes [ i ] ) )
continue ;
Util : : Rectangle rect = getNormalizedVisiblePaneArea ( panes [ i ] ) ;
if ( rect . intersects ( invalidateRect ) ) {
paneRects [ numPanes + + ] = rect ;
}
}
// We can ignore the invalidation if it's outside of all split-panes.
if ( ! numPanes )
2018-09-26 22:16:11 +02:00
return ;
2019-10-08 11:23:29 +02:00
if ( part = = - 1 ) // If no part is specified we use the part used by the client
2018-05-31 20:20:09 +02:00
part = _clientSelectedPart ;
2020-01-02 22:11:54 +01:00
int normalizedViewId = getCanonicalViewId ( ) ;
2019-10-14 13:12:26 +02:00
2018-05-31 20:20:09 +02:00
std : : vector < TileDesc > invalidTiles ;
2022-08-30 07:27:44 +02:00
if ( ( part = = _clientSelectedPart & & mode = = _clientSelectedMode ) | | _isTextDocument )
2018-05-31 20:20:09 +02:00
{
2020-07-08 03:40:13 +02:00
for ( int paneIdx = 0 ; paneIdx < numPanes ; + + paneIdx )
2018-05-31 20:20:09 +02:00
{
2020-07-08 03:40:13 +02:00
const Util : : Rectangle & normalizedVisArea = paneRects [ paneIdx ] ;
2023-12-20 15:59:29 +01:00
int lastVertTile = std : : ceil ( normalizedVisArea . getBottom ( ) / static_cast < double > ( _tileHeightTwips ) ) ;
int lastHoriTile = std : : ceil ( normalizedVisArea . getRight ( ) / static_cast < double > ( _tileWidthTwips ) ) ;
2020-07-08 03:40:13 +02:00
// Iterate through visible tiles
2023-12-20 15:59:29 +01:00
for ( int i = normalizedVisArea . getTop ( ) / _tileHeightTwips ; i < = lastVertTile ; + + i )
2018-05-31 20:20:09 +02:00
{
2023-12-20 15:59:29 +01:00
for ( int j = normalizedVisArea . getLeft ( ) / _tileWidthTwips ; j < = lastHoriTile ; + + j )
2018-05-31 20:20:09 +02:00
{
2020-07-08 03:40:13 +02:00
// Find tiles affected by invalidation
Util : : Rectangle tileRect ( j * _tileWidthTwips , i * _tileHeightTwips , _tileWidthTwips , _tileHeightTwips ) ;
if ( invalidateRect . intersects ( tileRect ) )
{
2022-08-30 07:27:44 +02:00
TileDesc desc ( normalizedViewId , part , mode ,
_tileWidthPixel , _tileHeightPixel ,
2022-06-15 17:38:11 +02:00
j * _tileWidthTwips , i * _tileHeightTwips ,
2023-09-07 16:25:37 +02:00
_tileWidthTwips , _tileHeightTwips , - 1 , 0 , - 1 ) ;
2018-07-06 12:38:31 +02:00
2022-06-15 17:38:11 +02:00
bool dup = false ;
// Check we don't have duplicates
for ( const auto & it : invalidTiles )
{
if ( it = = desc )
{
LOG_TRC ( " Duplicate tile skipped from invalidation " < < desc . debugName ( ) ) ;
dup = true ;
break ;
}
}
if ( ! dup )
{
invalidTiles . push_back ( desc ) ;
2023-12-07 20:42:55 +01:00
TileWireId makeDelta = 1 ;
// FIXME: mobile with no TileCache & flushed kit cache
// FIXME: out of (a)sync kit vs. TileCache re: keyframes ?
if ( getDocumentBroker ( ) - > hasTileCache ( ) & &
! getDocumentBroker ( ) - > tileCache ( ) . lookupTile ( desc ) )
makeDelta = 0 ; // force keyframe
invalidTiles . back ( ) . setOldWireId ( makeDelta ) ;
2022-06-15 17:38:11 +02:00
invalidTiles . back ( ) . setWireId ( 0 ) ;
}
2020-07-08 03:40:13 +02:00
}
2018-05-31 20:20:09 +02:00
}
}
}
}
if ( ! invalidTiles . empty ( ) )
{
TileCombined tileCombined = TileCombined : : create ( invalidTiles ) ;
2019-10-15 14:35:35 +02:00
tileCombined . setNormalizedViewId ( normalizedViewId ) ;
2022-05-25 11:33:27 +02:00
docBroker - > handleTileCombinedRequest ( tileCombined , false , client_from_this ( ) ) ;
2018-05-31 20:20:09 +02:00
}
}
2020-07-08 03:40:13 +02:00
bool ClientSession : : isSplitPane ( const SplitPaneName paneName ) const
{
if ( paneName = = BOTTOMRIGHT_PANE )
return true ;
if ( paneName = = TOPLEFT_PANE )
return ( _splitX & & _splitY ) ;
if ( paneName = = TOPRIGHT_PANE )
return _splitY ;
if ( paneName = = BOTTOMLEFT_PANE )
return _splitX ;
return false ;
}
Util : : Rectangle ClientSession : : getNormalizedVisiblePaneArea ( const SplitPaneName paneName ) const
{
Util : : Rectangle normalizedVisArea = getNormalizedVisibleArea ( ) ;
if ( ! _splitX & & ! _splitY )
return paneName = = BOTTOMRIGHT_PANE ? normalizedVisArea : Util : : Rectangle ( ) ;
int freeStartX = normalizedVisArea . getLeft ( ) + _splitX ;
int freeStartY = normalizedVisArea . getTop ( ) + _splitY ;
int freeWidth = normalizedVisArea . getWidth ( ) - _splitX ;
int freeHeight = normalizedVisArea . getHeight ( ) - _splitY ;
switch ( paneName )
{
case BOTTOMRIGHT_PANE :
return Util : : Rectangle ( freeStartX , freeStartY , freeWidth , freeHeight ) ;
case TOPLEFT_PANE :
return ( _splitX & & _splitY ) ? Util : : Rectangle ( 0 , 0 , _splitX , _splitY ) : Util : : Rectangle ( ) ;
case TOPRIGHT_PANE :
return _splitY ? Util : : Rectangle ( freeStartX , 0 , freeWidth , _splitY ) : Util : : Rectangle ( ) ;
case BOTTOMLEFT_PANE :
return _splitX ? Util : : Rectangle ( 0 , freeStartY , _splitX , freeHeight ) : Util : : Rectangle ( ) ;
default :
assert ( false & & " Unknown split-pane name " ) ;
}
return Util : : Rectangle ( ) ;
}
bool ClientSession : : isTileInsideVisibleArea ( const TileDesc & tile ) const
{
if ( ! _splitX & & ! _splitY )
{
return ( tile . getTilePosX ( ) > = _clientVisibleArea . getLeft ( ) & & tile . getTilePosX ( ) < = _clientVisibleArea . getRight ( ) & &
tile . getTilePosY ( ) > = _clientVisibleArea . getTop ( ) & & tile . getTilePosY ( ) < = _clientVisibleArea . getBottom ( ) ) ;
}
constexpr SplitPaneName panes [ 4 ] = {
TOPLEFT_PANE ,
TOPRIGHT_PANE ,
BOTTOMLEFT_PANE ,
BOTTOMRIGHT_PANE
} ;
for ( int i = 0 ; i < 4 ; + + i )
{
if ( ! isSplitPane ( panes [ i ] ) )
continue ;
Util : : Rectangle paneRect = getNormalizedVisiblePaneArea ( panes [ i ] ) ;
if ( tile . getTilePosX ( ) > = paneRect . getLeft ( ) & & tile . getTilePosX ( ) < = paneRect . getRight ( ) & &
tile . getTilePosY ( ) > = paneRect . getTop ( ) & & tile . getTilePosY ( ) < = paneRect . getBottom ( ) )
return true ;
}
return false ;
}
2024-01-24 11:14:45 +01:00
// This removes the <div id="meta-origin" ...> tag which was added in
2021-10-22 09:20:19 +02:00
// ClientSession::postProcessCopyPayload(), else the payload parsing
// in ChildSession::setClipboard() will fail.
// To see why, refer
// 1. ChildSession::getClipboard() where the data for various
// flavours along with flavour-type and length fields are packed into the payload.
// 2. The clipboard payload parsing code in ClipboardData::read().
void ClientSession : : preProcessSetClipboardPayload ( std : : string & payload )
{
2024-01-24 11:14:45 +01:00
std : : size_t start = payload . find ( " <div id= \" meta-origin \" data-coolorigin= \" " ) ;
2024-01-16 15:41:51 +01:00
if ( start ! = std : : string : : npos )
{
std : : size_t end = payload . find ( " \" > \n " , start ) ;
if ( end = = std : : string : : npos )
{
LOG_DBG ( " Found unbalanced starting meta <div> tag in setclipboard payload. " ) ;
return ;
}
std : : size_t len = end - start + 3 ;
payload . erase ( start , len ) ;
2024-01-19 12:56:31 +01:00
start = payload . find ( " </div></body> " ) ;
2024-01-16 15:41:51 +01:00
if ( start = = std : : string : : npos )
{
LOG_DBG ( " Found unbalanced ending meta <div> tag in setclipboard payload. " ) ;
return ;
}
2024-01-19 12:56:31 +01:00
payload . erase ( start , strlen ( " </div> " ) ) ;
2024-01-16 15:41:51 +01:00
}
2021-10-22 09:20:19 +02:00
}
2022-10-25 14:30:12 +02:00
std : : string ClientSession : : processSVGContent ( const std : : string & svg )
{
const std : : shared_ptr < DocumentBroker > docBroker = _docBroker . lock ( ) ;
if ( ! docBroker )
{
LOG_ERR ( " No DocBroker to process SVG content " ) ;
return svg ;
}
bool broken = false ;
std : : ostringstream oss ;
std : : string : : size_type pos = 0 ;
for ( ; ; )
{
static const std : : string prefix = " src= \" file:///tmp/ " ;
const auto start = svg . find ( prefix , pos ) ;
if ( start = = std : : string : : npos )
{
// Copy the rest and finish.
oss < < svg . substr ( pos ) ;
break ;
}
const auto startFilename = start + prefix . size ( ) ;
const auto end = svg . find ( ' " ' , startFilename ) ;
2022-10-27 14:44:57 +02:00
if ( end = = std : : string : : npos )
2022-10-25 14:30:12 +02:00
{
// Broken file; leave it as-is. Better to have no video than no slideshow.
broken = true ;
break ;
}
2022-10-27 14:44:57 +02:00
auto dot = svg . find ( ' . ' , startFilename ) ;
if ( dot = = std : : string : : npos | | dot > end )
dot = end ;
2022-10-25 14:30:12 +02:00
const std : : string id = svg . substr ( startFilename , dot - startFilename ) ;
oss < < svg . substr ( pos , start - pos ) ;
// Store the original json with the internal, temporary, file URI.
const std : : string fileUrl = svg . substr ( start + 5 , end - start - 5 ) ;
docBroker - > addEmbeddedMedia ( id , " { \" action \" : \" update \" , \" id \" : \" " + id + " \" , \" url \" : \" " +
fileUrl + " \" } " ) ;
const std : : string mediaUrl =
Util : : encodeURIComponent ( createPublicURI ( " media " , id , /*encode=*/ false ) , " & " ) ;
oss < < " src= \" " < < mediaUrl < < ' " ' ;
pos = end + 1 ;
}
return broken ? svg : oss . str ( ) ;
}
2016-05-16 13:37:02 +02:00
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */