libreoffice-online/loolwsd/LOOLSession.hpp
Pranav Kant 9417ec839c loolwsd: Introduce headless peer;fix convert-to API partially
Convert-to API got broken in
80786cc79d

In case of convert-to API, there is no actual client facing
websocket connection (and we use an invalid websocket
connection). So, when prisoner session tries to forward messages to
client it would fail as we have now started to propagate errors
from forwardToPeer.

Introduce a headless (without an actual client) peer mode, and
fail silently when someone tries to forward messages to such a peer.

Change-Id: I8a9f93f798dca9fea45c41e99bf373cd23d32e2c
2016-07-21 12:37:44 +05:30

207 lines
6.3 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* This file is part of the LibreOffice project.
*
* 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/.
*/
#ifndef INCLUDED_LOOLSESSION_HPP
#define INCLUDED_LOOLSESSION_HPP
#include <atomic>
#include <cassert>
#include <memory>
#include <mutex>
#include <ostream>
#include <Poco/Net/WebSocket.h>
#include <Poco/Buffer.h>
#include <Poco/Path.h>
#include <Poco/Process.h>
#include <Poco/StringTokenizer.h>
#include <Poco/Types.h>
#include "MessageQueue.hpp"
#include "LOOLProtocol.hpp"
#include "TileCache.hpp"
#include "Log.hpp"
class LOOLSession
{
public:
/// We have three kinds of Websocket sessions
/// 1) Between the master loolwsd server to the end-user LOOL client
/// 2) Between the master loolwsd server and a jailed child process, in the master process
/// 3) Ditto, in the jailed process
enum class Kind { ToClient, ToPrisoner, ToMaster };
const std::string& getId() const { return _id; }
const std::string& getName() const { return _name; }
bool isDisconnected() const { return _disconnected; }
bool sendTextFrame(const std::string& text);
bool sendBinaryFrame(const char *buffer, int length);
bool handleInput(const char *buffer, int length);
/// Invoked when we want to disconnect a session.
virtual void disconnect();
/// Called to handle disconnection command from socket.
virtual bool handleDisconnect();
void shutdown(Poco::UInt16 statusCode, const std::string& message)
{
if (_ws)
{
_ws->shutdown(statusCode, message);
}
}
bool isActive() const { return _isActive; }
void setIsActive(bool active) { _isActive = active; }
/// Returns the inactivity time of the client in milliseconds.
double getInactivityMS() const
{
const auto duration = (std::chrono::steady_clock::now() - _lastActivityTime);
return std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
}
void closeFrame() { _isCloseFrame = true; };
bool isCloseFrame() const { return _isCloseFrame; }
Kind getKind() const { return _kind; }
void setHeadless(bool val) { _isHeadless = val; }
bool isHeadless() const { return _isHeadless; }
protected:
LOOLSession(const std::string& id, const Kind kind,
std::shared_ptr<Poco::Net::WebSocket> ws);
virtual ~LOOLSession();
void setId(const std::string& id)
{
_id = id;
_name = _kindString + '-' + id;
}
/// Parses the options of the "load" command, shared between MasterProcessSession::loadDocument() and ChildProcessSession::loadDocument().
void parseDocOptions(const Poco::StringTokenizer& tokens, int& part, std::string& timestamp);
void updateLastActivityTime()
{
_lastActivityTime = std::chrono::steady_clock::now();
}
template <typename T>
bool forwardToPeer(T& p, const char *buffer, int length)
{
const auto message = LOOLProtocol::getAbbreviatedMessage(buffer, length);
auto peer = p.lock();
if (!peer)
{
throw Poco::ProtocolException(getName() + ": no peer to forward to: [" + message + "].");
}
else if (peer->isCloseFrame())
{
Log::trace(getName() + ": peer began the closing handshake. Dropping forward message [" + message + "].");
return false;
}
else if (peer->isHeadless())
{
// Fail silently and return as there is no actual websocket
// connection in this case.
Log::info(getName() + ": Ignoring forward message due to peer being headless");
return true;
}
Log::trace(getName() + " -> " + peer->getName() + ": " + message);
return peer->sendBinaryFrame(buffer, length);
}
private:
// Our kind signifies to what we are connected to.
const Kind _kind;
// The kind cached as a string.
const std::string _kindString;
// In the master process, the websocket to the LOOL client or the jailed child process. In a
// jailed process, the websocket to the parent.
std::shared_ptr<Poco::Net::WebSocket> _ws;
protected:
// The actual URL, also in the child, even if the child never accesses that.
std::string _docURL;
// The Jailed document path.
std::string _jailedFilePath;
// Password provided, if any, to open the document
std::string _docPassword;
// If password is provided or not
bool _haveDocPassword;
// Whether document has been opened succesfuly
bool _isDocLoaded;
// Whether document is password protected
bool _isDocPasswordProtected;
/// Document options: a JSON string, containing options (rendering, also possibly load in the future).
std::string _docOptions;
// Whether websocket received close frame. Closing Handshake
std::atomic<bool> _isCloseFrame;
private:
virtual bool _handleInput(const char *buffer, int length) = 0;
private:
/// A session ID specific to an end-to-end connection (from user to lokit).
std::string _id;
/// A readable name that identifies our peer and ID.
std::string _name;
/// True if we have been disconnected.
bool _disconnected;
/// True if the user is active, otherwise false (switched tabs).
bool _isActive;
std::chrono::steady_clock::time_point _lastActivityTime;
// Whether it is dummy session
// For eg. In case of convert-to requests (HTTP Post), there is no actual websocket
// connection on client side
bool _isHeadless;
std::mutex _mutex;
static constexpr auto InactivityThresholdMS = 120 * 1000;
};
template<typename charT, typename traits>
inline std::basic_ostream<charT, traits> & operator <<(std::basic_ostream<charT, traits> & stream, LOOLSession::Kind kind)
{
switch (kind)
{
case LOOLSession::Kind::ToClient:
return stream << "TO_CLIENT";
case LOOLSession::Kind::ToPrisoner:
return stream << "TO_PRISONER";
case LOOLSession::Kind::ToMaster:
return stream << "TO_MASTER";
default:
assert(false);
return stream << "UNK_" + std::to_string(static_cast<int>(kind));
}
}
#endif
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */