/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * Copyright the Collabora Online contributors. * * SPDX-License-Identifier: MPL-2.0 * * 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/. */ #pragma once #include #include #include #include #include #include #include #include #include #include "Socket.hpp" #define LOK_USE_UNSTABLE_API #include #if MOBILEAPP #include "ClientSession.hpp" #include "DocumentBroker.hpp" #endif void lokit_main( #if !MOBILEAPP const std::string& childRoot, const std::string& jailId, const std::string& sysTemplate, const std::string& loTemplate, bool noCapabilities, bool noSeccomp, bool useMountNamespaces, bool queryVersionInfo, bool displayVersion, #else int docBrokerSocket, const std::string& userInterface, #endif std::size_t numericIdentifier); #ifdef IOS void runKitLoopInAThread(); #endif bool globalPreinit(const std::string& loTemplate); /// Wrapper around private Document::ViewCallback(). void documentViewCallback(const int type, const char* p, void* data); class Document; class DeltaGenerator; /// Descriptor class used to link a LOK /// callback to a specific view. struct CallbackDescriptor { CallbackDescriptor(Document* const doc, const int viewId) : _doc(doc), _viewId(viewId) { } Document* getDoc() const { return _doc; } int getViewId() const { return _viewId; } private: Document* const _doc; const int _viewId; }; /// User Info container used to store user information /// till the end of process lifecycle - including /// after any child session goes away struct UserInfo { UserInfo() : _readOnly(false) , _connected(false) { } UserInfo(const std::string& userId, const std::string& userName, const std::string& userExtraInfo, const std::string& userPrivateInfo, bool readOnly) : _userId(userId) , _userName(userName) , _userExtraInfo(userExtraInfo) , _userPrivateInfo(userPrivateInfo) , _readOnly(readOnly) , _connected(true) { } const std::string& getUserId() const { return _userId; } const std::string& getUserName() const { return _userName; } const std::string& getUserExtraInfo() const { return _userExtraInfo; } const std::string& getUserPrivateInfo() const { return _userPrivateInfo; } bool isReadOnly() const { return _readOnly; } bool isConnected() const { return _connected; } void setDisconnected() { _connected = false; } private: std::string _userId; std::string _userName; std::string _userExtraInfo; std::string _userPrivateInfo; bool _readOnly; bool _connected; }; /// We have two types of password protected documents /// 1) Documents which require password to view /// 2) Document which require password to modify enum class DocumentPasswordType { ToView, ToModify }; /// Check the ForkCounter, and if non-zero, fork more of them accordingly. void forkLibreOfficeKit(const std::string& childRoot, const std::string& sysTemplate, const std::string& loTemplate, bool useMountNamespaces); class Document; /// The main main-loop of the Kit process class KitSocketPoll final : public SocketPoll { std::chrono::steady_clock::time_point _pollEnd; std::shared_ptr _document; static KitSocketPoll* mainPoll; KitSocketPoll(); public: ~KitSocketPoll(); void drainQueue(); static void dumpGlobalState(std::ostream& oss); static std::shared_ptr create(); static void cleanupChildProcess(); virtual void wakeupHook() override; static KitSocketPoll* getMainPoll() { return mainPoll; } #if ENABLE_DEBUG struct ReEntrancyGuard { std::atomic& _count; ReEntrancyGuard(std::atomic& count) : _count(count) { count++; } ~ReEntrancyGuard() { _count--; } }; #endif int kitPoll(int timeoutMicroS); void setDocument(std::shared_ptr document) { _document = std::move(document); } // unusual LOK event from another thread, push into our loop to process. static bool pushToMainThread(LibreOfficeKitCallback callback, int type, const char* p, void* data); #ifdef IOS static std::mutex KSPollsMutex; static std::condition_variable KSPollsCV; static std::vector> KSPolls; std::mutex terminationMutex; std::condition_variable terminationCV; bool terminationFlag; #endif }; class KitQueue; class ChildSession; /// A document container. /// Owns LOKitDocument instance and connections. /// Manages the lifetime of a document. /// Technically, we can host multiple documents /// per process. But for security reasons don't. /// However, we could have a coolkit instance /// per user or group of users (a trusted circle). class Document final : public std::enable_shared_from_this { public: Document(const std::shared_ptr& loKit, const std::string& jailId, const std::string& docKey, const std::string& docId, const std::string& url, const std::shared_ptr& websocketHandler, unsigned mobileAppDocId); virtual ~Document(); const std::string& getUrl() const { return _url; } /// Post the message - in the unipoll world we're in the right thread anyway bool postMessage(const char* data, int size, const WSOpCode code) const; bool createSession(const std::string& sessionId); /// Purges dead connections and returns /// the remaining number of clients. /// Returns -1 on failure. std::size_t purgeSessions(); void setDocumentPassword(int passwordType); void renderTiles(TileCombined& tileCombined); bool sendTextFrame(const std::string& message) { return sendFrame(message.data(), message.size()); } bool sendFrame(const char* buffer, int length, WSOpCode opCode = WSOpCode::Text); void alertNotAsync() { // load unfortunately enables inputprocessing in some cases. if (processInputEnabled() && !_duringLoad) notifyAll("error: cmd=notasync kind=failure"); } void alertAllUsers(const std::string& cmd, const std::string& kind) { sendTextFrame("errortoall: cmd=" + cmd + " kind=" + kind); } /// Notify all views with the given message bool notifyAll(const std::string& msg) { // Broadcast updated viewinfo to all clients. return sendTextFrame("client-all " + msg); } unsigned getMobileAppDocId() const { return _mobileAppDocId; } /// See if we should clear out our memory void trimIfInactive(); void trimAfterInactivity(); // LibreOfficeKit callback entry points static void GlobalCallback(const int type, const char* p, void* data); static void ViewCallback(const int type, const char* p, void* data); private: /// Helper method to broadcast callback and its payload to all clients void broadcastCallbackToClients(const int type, const std::string& payload) { _queue->putCallback(-1, type, payload); } public: /// Request loading a document, or a new view, if one exists, /// and register callbacks. bool onLoad(const std::string& sessionId, const std::string& uriAnonym, const std::string& renderOpts); /// Unload a client session, which unloads the document /// if it is the last and only. void onUnload(const ChildSession& session); /// Get a view ID <-> UserInfo map. std::map getViewInfo() { return _sessionUserInfo; } int getEditorId() const { return _editorId; } bool isDocPasswordProtected() const { return _isDocPasswordProtected; } bool haveDocPassword() const { return _haveDocPassword; } std::string getDocPassword() const { return _docPassword; } DocumentPasswordType getDocPasswordType() const { return _docPasswordType; } void updateActivityHeader() const; bool joinThreads(); void startThreads(); bool forkToSave(const std::function &childSave, int viewId); void handleSaveMessage(const std::string &msg); /// Notify all views of viewId and their associated usernames void notifyViewInfo(); std::shared_ptr findSessionByViewId(int viewId); void invalidateCanonicalId(const std::string& sessionId); std::string getViewProps(const std::shared_ptr& session); void updateEditorSpeeds(int id, int speed); private: // Get the color value for all author names from the core std::map getViewColors(); std::string getDefaultTheme(const std::shared_ptr& session) const; std::shared_ptr load(const std::shared_ptr& session, const std::string& renderOpts); bool forwardToChild(const std::string& prefix, const std::vector& payload); static std::string makeRenderParams(const std::string& renderOpts, const std::string& userName, const std::string& spellOnline, const std::string& theme); bool isTileRequestInsideVisibleArea(const TileCombined& tileCombined); public: bool processInputEnabled() const; /// A new message from wsd for the queue void queueMessage(const std::string &msg) { _queue->put(msg); } bool hasQueueItems() const { return _queue && !_queue->isEmpty(); } bool hasCallbacks() const { return _queue && _queue->callbackSize() > 0; } /// Should we get through the SocketPoll fast to process queus ? bool needsQuickPoll() const { if (hasCallbacks()) return true; if (hasQueueItems() && processInputEnabled()) return true; return false; } // poll is idle, are we ? void checkIdle(); void drainQueue(); void drainCallbacks(); void dumpState(std::ostream& oss); /// Return access to the lok::Office instance. std::shared_ptr getLOKit() { return _loKit; } /// Return access to the lok::Document instance. std::shared_ptr getLOKitDocument(); std::string getObfuscatedFileId() { return _obfuscatedFileId; } bool isBackgroundSaveProcess() const { return _isBgSaveProcess; } /// Save is async, so we need to set 'unmodified' while we are saving /// but this can transition back to modified if save fails. STATE_ENUM(ModifiedState, Modified, UnModifiedButSaving, UnModified); ModifiedState getModified() const { return _modified; } /// Lets us get notified whether a doc is modified during bgsave. void forceDocUnmodifiedForBgSave(int viewId); /// Restore the Document's 'modified' state if necessary void updateModifiedOnFailedBgSave(); /// Let WSD know our true modified state affter bg save success. void notifySyntheticUnmodifiedState(); /// Snoop document modified, and return true if filtering notification bool trackDocModifiedState(const std::string &stateChanged); /// Permanantly disable background save for this process void disableBgSave(const std::string &reason); /// Are we currently performing a load ? bool isLoadOngoing() const { return _duringLoad > 0; } private: void postForceModifiedCommand(bool modified); /// Stops theads, flushes buffers, and exits the process. void flushAndExit(int code); private: std::shared_ptr _loKit; const std::string _jailId; /// URL-based key. May be repeated during the lifetime of WSD. const std::string _docKey; /// Short numerical ID. Unique during the lifetime of WSD. const std::string _docId; const std::string _url; const std::string _obfuscatedFileId; std::string _jailedUrl; std::string _renderOpts; std::shared_ptr _loKitDocument; #ifdef __ANDROID__ static std::shared_ptr _loKitDocumentForAndroidOnly; #endif std::shared_ptr _queue; // Connection to the coolwsd process std::shared_ptr _websocketHandler; // Connection a child background save process has to its parent: a precious thing. std::weak_ptr _saveProcessParent; ModifiedState _modified; bool _isBgSaveProcess; bool _isBgSaveDisabled; // Document password provided std::string _docPassword; // Whether password was provided or not bool _haveDocPassword; // Whether document is password protected bool _isDocPasswordProtected; // Whether password is required to view the document, or modify it DocumentPasswordType _docPasswordType; std::atomic _stop; ThreadPool _deltaPool; std::unique_ptr _deltaGen; std::condition_variable _cvLoading; int _editorId; bool _editorChangeWarning; std::map> _viewIdToCallbackDescr; SessionMap _sessions; /// The timestamp of the last memory trimming. std::chrono::steady_clock::time_point _lastMemTrimTime; std::map _lastUpdatedAt; std::map _speedCount; /// For showing disconnected user info in the doc repair dialog. std::map _sessionUserInfo; #ifdef __ANDROID__ friend std::shared_ptr getLOKDocumentForAndroidOnly(); #endif const unsigned _mobileAppDocId; int _duringLoad; }; /// main function of the forkit process or thread int forkit_main(int argc, char** argv); /// Anonymize the basename of filenames, preserving the path and extension. std::string anonymizeUrl(const std::string& url); /// Anonymize usernames. std::string anonymizeUsername(const std::string& username); /// Ensure there is no fatal system setup problem void consistencyCheckJail(); /// check how many theads we have currently int getCurrentThreadCount(); /// Fetch the latest montonically incrementing wire-id TileWireId getCurrentWireId(bool increment = false); #ifdef __ANDROID__ /// For the Android app, for now, we need access to the one and only document open to perform eg. saveAs() for printing. std::shared_ptr getLOKDocumentForAndroidOnly(); #endif extern _LibreOfficeKit* loKitPtr; /// Check if URP is enabled bool isURPEnabled(); /// Start a URP connection, checking if URP is enabled and there is not already an active URP session bool startURP(std::shared_ptr LOKit, void** ppURPContext); /// Ensure all recorded traces hit the disk void flushTraceEventRecordings(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */