[Libreoffice-commits] online.git: loolwsd/ClientSession.cpp loolwsd/ClientSession.hpp loolwsd/DocumentBroker.cpp loolwsd/DocumentBroker.hpp loolwsd/LOOLWSD.cpp loolwsd/Makefile.am loolwsd/MasterProcessSession.hpp loolwsd/PrisonerSession.cpp loolwsd/PrisonerSession.hpp

Ashod Nakashian ashod.nakashian at collabora.co.uk
Tue May 17 03:12:46 UTC 2016


 loolwsd/ClientSession.cpp        |  636 +++++++++++++++++++++++++++++++++++++++
 loolwsd/ClientSession.hpp        |  115 +++++++
 loolwsd/DocumentBroker.cpp       |    3 
 loolwsd/DocumentBroker.hpp       |    4 
 loolwsd/LOOLWSD.cpp              |    6 
 loolwsd/Makefile.am              |    4 
 loolwsd/MasterProcessSession.hpp |    2 
 loolwsd/PrisonerSession.cpp      |  636 +++++++++++++++++++++++++++++++++++++++
 loolwsd/PrisonerSession.hpp      |  114 ++++++
 9 files changed, 1515 insertions(+), 5 deletions(-)

New commits:
commit 73f0e2a190b98d3ff5c86792f1337df7d888fc1b
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Mon May 16 07:37:02 2016 -0400

    loolwsd: MasterProcessSession splitting into ClientSession and PrisonerSession
    
    Change-Id: I29bcc5f791acf2313830e21d102e25f2232329d1
    Reviewed-on: https://gerrit.libreoffice.org/25037
    Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>

diff --git a/loolwsd/ClientSession.cpp b/loolwsd/ClientSession.cpp
new file mode 100644
index 0000000..6da51ec
--- /dev/null
+++ b/loolwsd/ClientSession.cpp
@@ -0,0 +1,636 @@
+/* -*- 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/.
+ */
+
+#include "config.h"
+
+#include <Poco/FileStream.h>
+#include <Poco/JSON/Object.h>
+#include <Poco/JSON/Parser.h>
+#include <Poco/URI.h>
+#include <Poco/URIStreamOpener.h>
+
+#include "Common.hpp"
+#include "LOOLProtocol.hpp"
+#include "LOOLSession.hpp"
+#include "LOOLWSD.hpp"
+#include "MasterProcessSession.hpp"
+#include "Rectangle.hpp"
+#include "Storage.hpp"
+#include "TileCache.hpp"
+#include "IoUtil.hpp"
+#include "Util.hpp"
+#if 0
+using namespace LOOLProtocol;
+
+using Poco::Path;
+using Poco::StringTokenizer;
+
+MasterProcessSession::MasterProcessSession(const std::string& id,
+                                           const Kind kind,
+                                           std::shared_ptr<Poco::Net::WebSocket> ws,
+                                           std::shared_ptr<DocumentBroker> docBroker,
+                                           std::shared_ptr<BasicTileQueue> queue) :
+    LOOLSession(id, kind, ws),
+    _curPart(0),
+    _loadPart(-1),
+    _docBroker(docBroker),
+    _queue(queue)
+{
+    Log::info("MasterProcessSession ctor [" + getName() + "].");
+}
+
+MasterProcessSession::~MasterProcessSession()
+{
+    Log::info("~MasterProcessSession dtor [" + getName() + "].");
+
+    // Release the save-as queue.
+    _saveAsQueue.put("");
+}
+
+bool MasterProcessSession::_handleInput(const char *buffer, int length)
+{
+    const std::string firstLine = getFirstLine(buffer, length);
+    StringTokenizer tokens(firstLine, " ", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
+    Log::trace(getName() + ": handling [" + firstLine + "].");
+
+    if (LOOLProtocol::tokenIndicatesUserInteraction(tokens[0]))
+    {
+        // Keep track of timestamps of incoming client messages that indicate user activity.
+        updateLastActivityTime();
+    }
+
+    if (tokens[0] == "loolclient")
+    {
+        const auto versionTuple = ParseVersion(tokens[1]);
+        if (std::get<0>(versionTuple) != ProtocolMajorVersionNumber ||
+            std::get<1>(versionTuple) != ProtocolMinorVersionNumber)
+        {
+            sendTextFrame("error: cmd=loolclient kind=badversion");
+            return false;
+        }
+
+        sendTextFrame("loolserver " + GetProtocolVersion());
+        return true;
+    }
+
+    if (_kind == Kind::ToPrisoner)
+    {
+        // Note that this handles both forwarding requests from the client to the child process, and
+        // forwarding replies from the child process to the client. Or does it?
+
+        // Snoop at some messages and manipulate tile cache information as needed
+        auto peer = _peer.lock();
+
+        {
+            if (!peer)
+            {
+                throw Poco::ProtocolException("The session has not been assigned a peer.");
+            }
+
+            if (tokens[0] == "unocommandresult:")
+            {
+                const std::string stringMsg(buffer, length);
+                Log::info(getName() + "Command: " + stringMsg);
+                const auto index = stringMsg.find_first_of('{');
+                if (index != std::string::npos)
+                {
+                    const std::string stringJSON = stringMsg.substr(index);
+                    Poco::JSON::Parser parser;
+                    const auto result = parser.parse(stringJSON);
+                    const auto& object = result.extract<Poco::JSON::Object::Ptr>();
+                    if (object->get("commandName").toString() == ".uno:Save" &&
+                        object->get("success").toString() == "true")
+                    {
+                        _docBroker->save();
+                        return true;
+                    }
+                }
+            }
+
+            if (tokens[0] == "error:")
+            {
+                std::string errorCommand;
+                std::string errorKind;
+                if (getTokenString(tokens[1], "cmd", errorCommand) &&
+                    getTokenString(tokens[2], "kind", errorKind) )
+                {
+                    if (errorCommand == "load")
+                    {
+                        if (errorKind == "passwordrequired:to-view" ||
+                            errorKind == "passwordrequired:to-modify" ||
+                            errorKind == "wrongpassword")
+                        {
+                            forwardToPeer(buffer, length);
+                            peer->_bLoadError = true;
+                            return false;
+                        }
+                    }
+                }
+            }
+
+            if (tokens[0] == "curpart:" &&
+                tokens.count() == 2 &&
+                getTokenInteger(tokens[1], "part", _curPart))
+            {
+                return true;
+            }
+
+            if (tokens.count() == 2 && tokens[0] == "saveas:")
+            {
+                std::string url;
+                if (!getTokenString(tokens[1], "url", url))
+                    return true;
+
+                if (peer)
+                {
+                    // Save as completed, inform the other (Kind::ToClient)
+                    // MasterProcessSession about it.
+
+                    const std::string filePrefix("file:///");
+                    if (url.find(filePrefix) == 0)
+                    {
+                        // Rewrite file:// URLs, as they are visible to the outside world.
+                        const Path path(_docBroker->getJailRoot(), url.substr(filePrefix.length()));
+                        url = filePrefix + path.toString().substr(1);
+                    }
+                    peer->_saveAsQueue.put(url);
+                }
+
+                return true;
+            }
+            else if (tokens.count() == 2 && tokens[0] == "statechanged:")
+            {
+                StringTokenizer stateTokens(tokens[1], "=", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
+                if (stateTokens.count() == 2 && stateTokens[0] == ".uno:ModifiedStatus")
+                {
+                    if (_docBroker)
+                    {
+                        _docBroker->setModified(stateTokens[1] == "true");
+                    }
+                }
+            }
+        }
+
+        if (peer && !_isDocPasswordProtected)
+        {
+            if (tokens[0] == "tile:")
+            {
+                assert(!"Tile traffic should go through the DocumentBroker-LoKit WS.");
+            }
+            else if (tokens[0] == "status:")
+            {
+                _docBroker->setLoaded();
+                _docBroker->tileCache().saveTextFile(std::string(buffer, length), "status.txt");
+
+                // Forward the status response to the client.
+                forwardToPeer(buffer, length);
+
+                // And let clients know if they hold the edit lock.
+                std::string message = "editlock: ";
+                message += std::to_string(peer->isEditLocked());
+                Log::debug("Forwarding [" + message + "] in response to status.");
+                forwardToPeer(message.c_str(), message.size());
+                return true;
+            }
+            else if (tokens[0] == "commandvalues:")
+            {
+                const std::string stringMsg(buffer, length);
+                const auto index = stringMsg.find_first_of('{');
+                if (index != std::string::npos)
+                {
+                    const std::string stringJSON = stringMsg.substr(index);
+                    Poco::JSON::Parser parser;
+                    const auto result = parser.parse(stringJSON);
+                    const auto& object = result.extract<Poco::JSON::Object::Ptr>();
+                    const std::string commandName = object->get("commandName").toString();
+                    if (commandName.find(".uno:CharFontName") != std::string::npos ||
+                        commandName.find(".uno:StyleApply") != std::string::npos)
+                    {
+                        // other commands should not be cached
+                        _docBroker->tileCache().saveTextFile(stringMsg, "cmdValues" + commandName + ".txt");
+                    }
+                }
+            }
+            else if (tokens[0] == "partpagerectangles:")
+            {
+                if (tokens.count() > 1 && !tokens[1].empty())
+                    _docBroker->tileCache().saveTextFile(std::string(buffer, length), "partpagerectangles.txt");
+            }
+            else if (tokens[0] == "invalidatetiles:")
+            {
+                assert(firstLine.size() == static_cast<std::string::size_type>(length));
+                _docBroker->tileCache().invalidateTiles(firstLine);
+            }
+            else if (tokens[0] == "renderfont:")
+            {
+                std::string font;
+                if (tokens.count() < 2 ||
+                    !getTokenString(tokens[1], "font", font))
+                    assert(false);
+
+                assert(firstLine.size() < static_cast<std::string::size_type>(length));
+                _docBroker->tileCache().saveRendering(font, "font", buffer + firstLine.size() + 1, length - firstLine.size() - 1);
+            }
+        }
+
+        forwardToPeer(buffer, length);
+        return true;
+    }
+
+    if (_kind == Kind::ToPrisoner)
+    {
+        // Message from child process to be forwarded to client.
+
+        // I think we should never get here
+        Log::error(getName() + ": Unexpected request [" + tokens[0] + "].");
+        assert(false);
+    }
+    else if (tokens[0] == "takeedit")
+    {
+        _docBroker->takeEditLock(getId());
+        return true;
+    }
+    else if (tokens[0] == "load")
+    {
+        if (_docURL != "")
+        {
+            sendTextFrame("error: cmd=load kind=docalreadyloaded");
+            return false;
+        }
+        return loadDocument(buffer, length, tokens);
+    }
+    else if (tokens[0] != "canceltiles" &&
+             tokens[0] != "clientzoom" &&
+             tokens[0] != "clientvisiblearea" &&
+             tokens[0] != "commandvalues" &&
+             tokens[0] != "downloadas" &&
+             tokens[0] != "getchildid" &&
+             tokens[0] != "gettextselection" &&
+             tokens[0] != "paste" &&
+             tokens[0] != "insertfile" &&
+             tokens[0] != "key" &&
+             tokens[0] != "mouse" &&
+             tokens[0] != "partpagerectangles" &&
+             tokens[0] != "renderfont" &&
+             tokens[0] != "requestloksession" &&
+             tokens[0] != "resetselection" &&
+             tokens[0] != "saveas" &&
+             tokens[0] != "selectgraphic" &&
+             tokens[0] != "selecttext" &&
+             tokens[0] != "setclientpart" &&
+             tokens[0] != "setpage" &&
+             tokens[0] != "status" &&
+             tokens[0] != "tile" &&
+             tokens[0] != "tilecombine" &&
+             tokens[0] != "uno" &&
+             tokens[0] != "useractive" &&
+             tokens[0] != "userinactive")
+    {
+        sendTextFrame("error: cmd=" + tokens[0] + " kind=unknown");
+        return false;
+    }
+    else if (_docURL == "")
+    {
+        sendTextFrame("error: cmd=" + tokens[0] + " kind=nodocloaded");
+        return false;
+    }
+    else if (tokens[0] == "canceltiles")
+    {
+        if (!_peer.expired())
+            forwardToPeer(buffer, length);
+    }
+    else if (tokens[0] == "commandvalues")
+    {
+        return getCommandValues(buffer, length, tokens);
+    }
+    else if (tokens[0] == "partpagerectangles")
+    {
+        return getPartPageRectangles(buffer, length);
+    }
+    else if (tokens[0] == "renderfont")
+    {
+        sendFontRendering(buffer, length, tokens);
+    }
+    else if (tokens[0] == "status")
+    {
+        return getStatus(buffer, length);
+    }
+    else if (tokens[0] == "tile")
+    {
+        sendTile(buffer, length, tokens);
+    }
+    else if (tokens[0] == "tilecombine")
+    {
+        sendCombinedTiles(buffer, length, tokens);
+    }
+    else
+    {
+        // All other commands are such that they always require a
+        // LibreOfficeKitDocument session, i.e. need to be handled in
+        // a child process.
+
+        if (_peer.expired())
+        {
+            Log::trace("Dispatching child to handle [" + tokens[0] + "].");
+            dispatchChild();
+        }
+
+        // Allow 'downloadas' for all kinds of views irrespective of editlock
+        if (_kind == Kind::ToClient && !isEditLocked() && tokens[0] != "downloadas" &&
+            tokens[0] != "userinactive" && tokens[0] != "useractive")
+        {
+            std::string dummyFrame = "dummymsg";
+            forwardToPeer(dummyFrame.c_str(), dummyFrame.size());
+        }
+        else if (tokens[0] != "requestloksession")
+        {
+            forwardToPeer(buffer, length);
+        }
+    }
+    return true;
+}
+
+bool MasterProcessSession::loadDocument(const char* /*buffer*/, int /*length*/, StringTokenizer& tokens)
+{
+    if (tokens.count() < 2)
+    {
+        sendTextFrame("error: cmd=load kind=syntax");
+        return false;
+    }
+
+    try
+    {
+        std::string timestamp;
+        parseDocOptions(tokens, _loadPart, timestamp);
+
+        // Finally, wait for the Child to connect to Master,
+        // link the document in jail and dispatch load to child.
+        Log::trace("Dispatching child to handle [load].");
+        dispatchChild();
+
+        return true;
+    }
+    catch (const Poco::SyntaxException&)
+    {
+        sendTextFrame("error: cmd=load kind=uriinvalid");
+    }
+
+    return false;
+}
+
+bool MasterProcessSession::getStatus(const char *buffer, int length)
+{
+    const std::string status = _docBroker->tileCache().getTextFile("status.txt");
+    if (!status.empty())
+    {
+        sendTextFrame(status);
+
+        // And let clients know if they hold the edit lock.
+        std::string message = "editlock: ";
+        message += std::to_string(isEditLocked());
+        Log::debug("Forwarding [" + message + "] in response to status.");
+        sendTextFrame(message);
+
+        return true;
+    }
+
+    if (_peer.expired())
+    {
+        Log::trace("Dispatching child to handle [getStatus].");
+        dispatchChild();
+    }
+
+    forwardToPeer(buffer, length);
+    return true;
+}
+
+void MasterProcessSession::setEditLock(const bool value)
+{
+    // Update the sate and forward to child.
+    _bEditLock = value;
+    const auto msg = std::string("editlock: ") + (value ? "1" : "0");
+    forwardToPeer(msg.data(), msg.size());
+}
+
+bool MasterProcessSession::getCommandValues(const char *buffer, int length, StringTokenizer& tokens)
+{
+    std::string command;
+    if (tokens.count() != 2 || !getTokenString(tokens[1], "command", command))
+    {
+        sendTextFrame("error: cmd=commandvalues kind=syntax");
+        return false;
+    }
+
+    const std::string cmdValues = _docBroker->tileCache().getTextFile("cmdValues" + command + ".txt");
+    if (cmdValues.size() > 0)
+    {
+        sendTextFrame(cmdValues);
+        return true;
+    }
+
+    if (_peer.expired())
+        dispatchChild();
+    forwardToPeer(buffer, length);
+    return true;
+}
+
+bool MasterProcessSession::getPartPageRectangles(const char *buffer, int length)
+{
+    const std::string partPageRectangles = _docBroker->tileCache().getTextFile("partpagerectangles.txt");
+    if (partPageRectangles.size() > 0)
+    {
+        sendTextFrame(partPageRectangles);
+        return true;
+    }
+
+    if (_peer.expired())
+        dispatchChild();
+    forwardToPeer(buffer, length);
+    return true;
+}
+
+std::string MasterProcessSession::getSaveAs()
+{
+    const auto payload = _saveAsQueue.get();
+    return std::string(payload.data(), payload.size());
+}
+
+void MasterProcessSession::sendFontRendering(const char *buffer, int length, StringTokenizer& tokens)
+{
+    std::string font;
+    if (tokens.count() < 2 ||
+        !getTokenString(tokens[1], "font", font))
+    {
+        sendTextFrame("error: cmd=renderfont kind=syntax");
+        return;
+    }
+
+    const std::string response = "renderfont: " + Poco::cat(std::string(" "), tokens.begin() + 1, tokens.end()) + "\n";
+
+    std::vector<char> output;
+    output.resize(response.size());
+    std::memcpy(output.data(), response.data(), response.size());
+
+    std::unique_ptr<std::fstream> cachedRendering = _docBroker->tileCache().lookupRendering(font, "font");
+    if (cachedRendering && cachedRendering->is_open())
+    {
+        cachedRendering->seekg(0, std::ios_base::end);
+        size_t pos = output.size();
+        std::streamsize size = cachedRendering->tellg();
+        output.resize(pos + size);
+        cachedRendering->seekg(0, std::ios_base::beg);
+        cachedRendering->read(output.data() + pos, size);
+        cachedRendering->close();
+
+        sendBinaryFrame(output.data(), output.size());
+        return;
+    }
+
+    if (_peer.expired())
+        dispatchChild();
+    forwardToPeer(buffer, length);
+}
+
+void MasterProcessSession::sendTile(const char * /*buffer*/, int /*length*/, StringTokenizer& tokens)
+{
+    try
+    {
+        auto tileDesc = TileDesc::parse(tokens);
+        _docBroker->handleTileRequest(tileDesc, shared_from_this());
+    }
+    catch (const std::exception& exc)
+    {
+        Log::error(std::string("Failed to process tile command: ") + exc.what() + ".");
+        sendTextFrame("error: cmd=tile kind=invalid");
+    }
+}
+
+void MasterProcessSession::sendCombinedTiles(const char* /*buffer*/, int /*length*/, StringTokenizer& tokens)
+{
+    int part, pixelWidth, pixelHeight, tileWidth, tileHeight;
+    std::string tilePositionsX, tilePositionsY;
+    if (tokens.count() < 8 ||
+        !getTokenInteger(tokens[1], "part", part) ||
+        !getTokenInteger(tokens[2], "width", pixelWidth) ||
+        !getTokenInteger(tokens[3], "height", pixelHeight) ||
+        !getTokenString (tokens[4], "tileposx", tilePositionsX) ||
+        !getTokenString (tokens[5], "tileposy", tilePositionsY) ||
+        !getTokenInteger(tokens[6], "tilewidth", tileWidth) ||
+        !getTokenInteger(tokens[7], "tileheight", tileHeight))
+    {
+        sendTextFrame("error: cmd=tilecombine kind=syntax");
+        return;
+    }
+
+    if (part < 0 || pixelWidth <= 0 || pixelHeight <= 0 ||
+        tileWidth <= 0 || tileHeight <= 0 ||
+        tilePositionsX.empty() || tilePositionsY.empty())
+    {
+        sendTextFrame("error: cmd=tilecombine kind=invalid");
+        return;
+    }
+
+    std::string reqTimestamp;
+    size_t index = 8;
+    if (tokens.count() > index && tokens[index].find("timestamp") == 0)
+    {
+        getTokenString(tokens[index], "timestamp", reqTimestamp);
+        ++index;
+    }
+
+    int id = -1;
+    if (tokens.count() > index && tokens[index].find("id") == 0)
+    {
+        getTokenInteger(tokens[index], "id", id);
+        ++index;
+    }
+
+    StringTokenizer positionXtokens(tilePositionsX, ",", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
+    StringTokenizer positionYtokens(tilePositionsY, ",", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
+
+    size_t numberOfPositions = positionYtokens.count();
+
+    // check that number of positions for X and Y is the same
+    if (numberOfPositions != positionXtokens.count())
+    {
+        sendTextFrame("error: cmd=tilecombine kind=invalid");
+        return;
+    }
+
+    for (size_t i = 0; i < numberOfPositions; ++i)
+    {
+        int x = 0;
+        if (!stringToInteger(positionXtokens[i], x))
+        {
+            sendTextFrame("error: cmd=tilecombine kind=syntax");
+            return;
+        }
+
+        int y = 0;
+        if (!stringToInteger(positionYtokens[i], y))
+        {
+            sendTextFrame("error: cmd=tilecombine kind=syntax");
+            return;
+        }
+
+        const TileDesc tile(part, pixelWidth, pixelHeight, x, y, tileWidth, tileHeight);
+        _docBroker->handleTileRequest(tile, shared_from_this());
+    }
+}
+
+void MasterProcessSession::dispatchChild()
+{
+    std::ostringstream oss;
+    oss << "load";
+    oss << " url=" << _docBroker->getPublicUri().toString();
+    oss << " jail=" << _docBroker->getJailedUri().toString();
+
+    if (_loadPart >= 0)
+        oss << " part=" + std::to_string(_loadPart);
+
+    if (_haveDocPassword)
+        oss << " password=" << _docPassword;
+
+    if (!_docOptions.empty())
+        oss << " options=" << _docOptions;
+
+    const auto loadRequest = oss.str();
+    forwardToPeer(loadRequest.c_str(), loadRequest.size());
+}
+
+void MasterProcessSession::forwardToPeer(const char *buffer, int length)
+{
+    const auto message = getAbbreviatedMessage(buffer, length);
+
+    auto peer = _peer.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;
+    }
+
+    Log::trace(getName() + " -> " + peer->getName() + ": " + message);
+    peer->sendBinaryFrame(buffer, length);
+}
+
+bool MasterProcessSession::shutdownPeer(Poco::UInt16 statusCode, const std::string& message)
+{
+    auto peer = _peer.lock();
+    if (peer && !peer->isCloseFrame())
+    {
+        peer->_ws->shutdown(statusCode, message);
+    }
+    return peer != nullptr;
+}
+#endif
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/loolwsd/ClientSession.hpp b/loolwsd/ClientSession.hpp
new file mode 100644
index 0000000..fcbc152
--- /dev/null
+++ b/loolwsd/ClientSession.hpp
@@ -0,0 +1,115 @@
+/* -*- 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_CLIENTSSESSION_HPP
+#define INCLUDED_CLIENTSSESSION_HPP
+
+#include <time.h>
+
+#include <Poco/Random.h>
+
+#include "MasterProcessSession.hpp"
+#include "LOOLSession.hpp"
+#include "MessageQueue.hpp"
+
+class DocumentBroker;
+class PrisonerSession;
+
+class ClientSession final : public MasterProcessSession//, public std::enable_shared_from_this<ClientSession>
+{
+public:
+    using MasterProcessSession::MasterProcessSession;
+
+    //void setPeer(const std::shared_ptr<PrisonerSession>& peer) { _peer = peer; }
+
+private:
+
+    //std::weak_ptr<PrisonerSession> _peer;
+
+#if 0
+ public:
+    MasterProcessSession(const std::string& id,
+                         const Kind kind,
+                         std::shared_ptr<Poco::Net::WebSocket> ws,
+                         std::shared_ptr<DocumentBroker> docBroker,
+                         std::shared_ptr<BasicTileQueue> queue);
+    virtual ~MasterProcessSession();
+
+    virtual bool getStatus(const char *buffer, int length) override;
+
+    virtual bool getCommandValues(const char *buffer, int length, Poco::StringTokenizer& tokens) override;
+
+    virtual bool getPartPageRectangles(const char *buffer, int length) override;
+
+    /**
+     * Return the URL of the saved-as document when it's ready. If called
+     * before it's ready, the call blocks till then.
+     */
+    std::string getSaveAs();
+
+    std::shared_ptr<DocumentBroker> getDocumentBroker() const { return _docBroker; }
+
+    std::shared_ptr<BasicTileQueue> getQueue() const { return _queue; }
+
+    void setPeer(const std::shared_ptr<MasterProcessSession>& peer) { _peer = peer; }
+
+    void setEditLock(const bool value);
+    void markEditLock(const bool value) { _bEditLock = value; }
+    bool isEditLocked() const { return _bEditLock; }
+
+    bool shutdownPeer(Poco::UInt16 statusCode, const std::string& message);
+
+public:
+    // Raise this flag on ToClient from ToPrisoner to let ToClient know of load failures
+    bool _bLoadError = false;
+
+ protected:
+    virtual bool loadDocument(const char *buffer, int length, Poco::StringTokenizer& tokens) override;
+
+    virtual void sendTile(const char *buffer, int length, Poco::StringTokenizer& tokens);
+
+    virtual void sendCombinedTiles(const char *buffer, int length, Poco::StringTokenizer& tokens);
+
+    virtual void sendFontRendering(const char *buffer, int length, Poco::StringTokenizer& tokens) override;
+
+ private:
+    void dispatchChild();
+    void forwardToPeer(const char *buffer, int length);
+
+    // If _kind==ToPrisoner and the child process has started and completed its handshake with the
+    // parent process: Points to the WebSocketSession for the child process handling the document in
+    // question, if any.
+
+    // In the session to the child process, points to the LOOLSession for the LOOL client. This will
+    // obvious have to be rethought when we add collaboration and there can be several LOOL clients
+    // per document being edited (i.e., per child process).
+    std::weak_ptr<MasterProcessSession> _peer;
+
+    static
+    Poco::Path getJailPath(const std::string& childId);
+
+    virtual bool _handleInput(const char *buffer, int length) override;
+
+    int _curPart;
+    int _loadPart;
+    /// Kind::ToClient instances store URLs of completed 'save as' documents.
+    MessageQueue _saveAsQueue;
+    std::shared_ptr<DocumentBroker> _docBroker;
+    std::shared_ptr<BasicTileQueue> _queue;
+
+    // If this document holds the edit lock.
+    // An edit lock will only allow the current session to make edits,
+    // while other session opening the same document can only see
+    bool _bEditLock = false;
+#endif
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/loolwsd/DocumentBroker.cpp b/loolwsd/DocumentBroker.cpp
index 35af2c3..e3735f9 100644
--- a/loolwsd/DocumentBroker.cpp
+++ b/loolwsd/DocumentBroker.cpp
@@ -18,6 +18,7 @@
 #include "Storage.hpp"
 #include "TileCache.hpp"
 #include "LOOLProtocol.hpp"
+#include "PrisonerSession.hpp"
 
 using namespace LOOLProtocol;
 
@@ -344,7 +345,7 @@ size_t DocumentBroker::addSession(std::shared_ptr<MasterProcessSession>& session
     return _sessions.size();
 }
 
-bool DocumentBroker::connectPeers(std::shared_ptr<MasterProcessSession>& session)
+bool DocumentBroker::connectPeers(std::shared_ptr<PrisonerSession>& session)
 {
     const auto id = session->getId();
 
diff --git a/loolwsd/DocumentBroker.hpp b/loolwsd/DocumentBroker.hpp
index 89b1036..20b9761 100644
--- a/loolwsd/DocumentBroker.hpp
+++ b/loolwsd/DocumentBroker.hpp
@@ -116,6 +116,8 @@ private:
     std::atomic<bool> _stop;
 };
 
+class PrisonerSession;
+
 /// DocumentBroker is responsible for setting up a document
 /// in jail and brokering loading it from Storage
 /// and saving it back.
@@ -193,7 +195,7 @@ public:
     /// Add a new session. Returns the new number of sessions.
     size_t addSession(std::shared_ptr<MasterProcessSession>& session);
     /// Connect a prison session to its client peer.
-    bool connectPeers(std::shared_ptr<MasterProcessSession>& session);
+    bool connectPeers(std::shared_ptr<PrisonerSession>& session);
     /// Removes a session by ID. Returns the new number of sessions.
     size_t removeSession(const std::string& id);
 
diff --git a/loolwsd/LOOLWSD.cpp b/loolwsd/LOOLWSD.cpp
index c414c0f..358c152 100644
--- a/loolwsd/LOOLWSD.cpp
+++ b/loolwsd/LOOLWSD.cpp
@@ -88,6 +88,8 @@
 #include "LOOLProtocol.hpp"
 #include "LOOLSession.hpp"
 #include "LOOLWSD.hpp"
+#include "ClientSession.hpp"
+#include "PrisonerSession.hpp"
 #include "MasterProcessSession.hpp"
 #include "QueueHandler.hpp"
 #include "Storage.hpp"
@@ -156,7 +158,7 @@ static std::mutex docBrokersMutex;
 // document to work on.
 static std::mutex AvailableChildSessionMutex;
 static std::condition_variable AvailableChildSessionCV;
-static std::map<std::string, std::shared_ptr<MasterProcessSession>> AvailableChildSessions;
+static std::map<std::string, std::shared_ptr<PrisonerSession>> AvailableChildSessions;
 
 #if ENABLE_DEBUG
 static int careerSpanSeconds = 0;
@@ -984,7 +986,7 @@ public:
             docBroker->load(jailId);
 
             auto ws = std::make_shared<WebSocket>(request, response);
-            auto session = std::make_shared<MasterProcessSession>(sessionId, LOOLSession::Kind::ToPrisoner, ws, docBroker, nullptr);
+            auto session = std::make_shared<PrisonerSession>(sessionId, LOOLSession::Kind::ToPrisoner, ws, docBroker, nullptr);
 
             // Connect the prison session to the client.
             docBroker->connectPeers(session);
diff --git a/loolwsd/Makefile.am b/loolwsd/Makefile.am
index 8d92a3f..8127013 100644
--- a/loolwsd/Makefile.am
+++ b/loolwsd/Makefile.am
@@ -42,6 +42,8 @@ loolwsd_SOURCES = Admin.cpp \
                   Auth.cpp \
                   DocumentBroker.cpp \
                   LOOLWSD.cpp \
+                  ClientSession.cpp \
+                  PrisonerSession.cpp \
                   MasterProcessSession.cpp \
                   Storage.cpp \
                   TileCache.cpp \
@@ -91,6 +93,8 @@ noinst_HEADERS = Admin.hpp \
                  LOOLSession.hpp \
                  LOOLWSD.hpp \
                  MasterProcessSession.hpp \
+                 ClientSession.hpp \
+                 PrisonerSession.hpp \
                  MessageQueue.hpp \
                  Png.hpp \
                  QueueHandler.hpp \
diff --git a/loolwsd/MasterProcessSession.hpp b/loolwsd/MasterProcessSession.hpp
index 19a4788..c8f0c61 100644
--- a/loolwsd/MasterProcessSession.hpp
+++ b/loolwsd/MasterProcessSession.hpp
@@ -19,7 +19,7 @@
 
 class DocumentBroker;
 
-class MasterProcessSession final : public LOOLSession, public std::enable_shared_from_this<MasterProcessSession>
+class MasterProcessSession : public LOOLSession, public std::enable_shared_from_this<MasterProcessSession>
 {
  public:
     MasterProcessSession(const std::string& id,
diff --git a/loolwsd/PrisonerSession.cpp b/loolwsd/PrisonerSession.cpp
new file mode 100644
index 0000000..6da51ec
--- /dev/null
+++ b/loolwsd/PrisonerSession.cpp
@@ -0,0 +1,636 @@
+/* -*- 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/.
+ */
+
+#include "config.h"
+
+#include <Poco/FileStream.h>
+#include <Poco/JSON/Object.h>
+#include <Poco/JSON/Parser.h>
+#include <Poco/URI.h>
+#include <Poco/URIStreamOpener.h>
+
+#include "Common.hpp"
+#include "LOOLProtocol.hpp"
+#include "LOOLSession.hpp"
+#include "LOOLWSD.hpp"
+#include "MasterProcessSession.hpp"
+#include "Rectangle.hpp"
+#include "Storage.hpp"
+#include "TileCache.hpp"
+#include "IoUtil.hpp"
+#include "Util.hpp"
+#if 0
+using namespace LOOLProtocol;
+
+using Poco::Path;
+using Poco::StringTokenizer;
+
+MasterProcessSession::MasterProcessSession(const std::string& id,
+                                           const Kind kind,
+                                           std::shared_ptr<Poco::Net::WebSocket> ws,
+                                           std::shared_ptr<DocumentBroker> docBroker,
+                                           std::shared_ptr<BasicTileQueue> queue) :
+    LOOLSession(id, kind, ws),
+    _curPart(0),
+    _loadPart(-1),
+    _docBroker(docBroker),
+    _queue(queue)
+{
+    Log::info("MasterProcessSession ctor [" + getName() + "].");
+}
+
+MasterProcessSession::~MasterProcessSession()
+{
+    Log::info("~MasterProcessSession dtor [" + getName() + "].");
+
+    // Release the save-as queue.
+    _saveAsQueue.put("");
+}
+
+bool MasterProcessSession::_handleInput(const char *buffer, int length)
+{
+    const std::string firstLine = getFirstLine(buffer, length);
+    StringTokenizer tokens(firstLine, " ", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
+    Log::trace(getName() + ": handling [" + firstLine + "].");
+
+    if (LOOLProtocol::tokenIndicatesUserInteraction(tokens[0]))
+    {
+        // Keep track of timestamps of incoming client messages that indicate user activity.
+        updateLastActivityTime();
+    }
+
+    if (tokens[0] == "loolclient")
+    {
+        const auto versionTuple = ParseVersion(tokens[1]);
+        if (std::get<0>(versionTuple) != ProtocolMajorVersionNumber ||
+            std::get<1>(versionTuple) != ProtocolMinorVersionNumber)
+        {
+            sendTextFrame("error: cmd=loolclient kind=badversion");
+            return false;
+        }
+
+        sendTextFrame("loolserver " + GetProtocolVersion());
+        return true;
+    }
+
+    if (_kind == Kind::ToPrisoner)
+    {
+        // Note that this handles both forwarding requests from the client to the child process, and
+        // forwarding replies from the child process to the client. Or does it?
+
+        // Snoop at some messages and manipulate tile cache information as needed
+        auto peer = _peer.lock();
+
+        {
+            if (!peer)
+            {
+                throw Poco::ProtocolException("The session has not been assigned a peer.");
+            }
+
+            if (tokens[0] == "unocommandresult:")
+            {
+                const std::string stringMsg(buffer, length);
+                Log::info(getName() + "Command: " + stringMsg);
+                const auto index = stringMsg.find_first_of('{');
+                if (index != std::string::npos)
+                {
+                    const std::string stringJSON = stringMsg.substr(index);
+                    Poco::JSON::Parser parser;
+                    const auto result = parser.parse(stringJSON);
+                    const auto& object = result.extract<Poco::JSON::Object::Ptr>();
+                    if (object->get("commandName").toString() == ".uno:Save" &&
+                        object->get("success").toString() == "true")
+                    {
+                        _docBroker->save();
+                        return true;
+                    }
+                }
+            }
+
+            if (tokens[0] == "error:")
+            {
+                std::string errorCommand;
+                std::string errorKind;
+                if (getTokenString(tokens[1], "cmd", errorCommand) &&
+                    getTokenString(tokens[2], "kind", errorKind) )
+                {
+                    if (errorCommand == "load")
+                    {
+                        if (errorKind == "passwordrequired:to-view" ||
+                            errorKind == "passwordrequired:to-modify" ||
+                            errorKind == "wrongpassword")
+                        {
+                            forwardToPeer(buffer, length);
+                            peer->_bLoadError = true;
+                            return false;
+                        }
+                    }
+                }
+            }
+
+            if (tokens[0] == "curpart:" &&
+                tokens.count() == 2 &&
+                getTokenInteger(tokens[1], "part", _curPart))
+            {
+                return true;
+            }
+
+            if (tokens.count() == 2 && tokens[0] == "saveas:")
+            {
+                std::string url;
+                if (!getTokenString(tokens[1], "url", url))
+                    return true;
+
+                if (peer)
+                {
+                    // Save as completed, inform the other (Kind::ToClient)
+                    // MasterProcessSession about it.
+
+                    const std::string filePrefix("file:///");
+                    if (url.find(filePrefix) == 0)
+                    {
+                        // Rewrite file:// URLs, as they are visible to the outside world.
+                        const Path path(_docBroker->getJailRoot(), url.substr(filePrefix.length()));
+                        url = filePrefix + path.toString().substr(1);
+                    }
+                    peer->_saveAsQueue.put(url);
+                }
+
+                return true;
+            }
+            else if (tokens.count() == 2 && tokens[0] == "statechanged:")
+            {
+                StringTokenizer stateTokens(tokens[1], "=", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
+                if (stateTokens.count() == 2 && stateTokens[0] == ".uno:ModifiedStatus")
+                {
+                    if (_docBroker)
+                    {
+                        _docBroker->setModified(stateTokens[1] == "true");
+                    }
+                }
+            }
+        }
+
+        if (peer && !_isDocPasswordProtected)
+        {
+            if (tokens[0] == "tile:")
+            {
+                assert(!"Tile traffic should go through the DocumentBroker-LoKit WS.");
+            }
+            else if (tokens[0] == "status:")
+            {
+                _docBroker->setLoaded();
+                _docBroker->tileCache().saveTextFile(std::string(buffer, length), "status.txt");
+
+                // Forward the status response to the client.
+                forwardToPeer(buffer, length);
+
+                // And let clients know if they hold the edit lock.
+                std::string message = "editlock: ";
+                message += std::to_string(peer->isEditLocked());
+                Log::debug("Forwarding [" + message + "] in response to status.");
+                forwardToPeer(message.c_str(), message.size());
+                return true;
+            }
+            else if (tokens[0] == "commandvalues:")
+            {
+                const std::string stringMsg(buffer, length);
+                const auto index = stringMsg.find_first_of('{');
+                if (index != std::string::npos)
+                {
+                    const std::string stringJSON = stringMsg.substr(index);
+                    Poco::JSON::Parser parser;
+                    const auto result = parser.parse(stringJSON);
+                    const auto& object = result.extract<Poco::JSON::Object::Ptr>();
+                    const std::string commandName = object->get("commandName").toString();
+                    if (commandName.find(".uno:CharFontName") != std::string::npos ||
+                        commandName.find(".uno:StyleApply") != std::string::npos)
+                    {
+                        // other commands should not be cached
+                        _docBroker->tileCache().saveTextFile(stringMsg, "cmdValues" + commandName + ".txt");
+                    }
+                }
+            }
+            else if (tokens[0] == "partpagerectangles:")
+            {
+                if (tokens.count() > 1 && !tokens[1].empty())
+                    _docBroker->tileCache().saveTextFile(std::string(buffer, length), "partpagerectangles.txt");
+            }
+            else if (tokens[0] == "invalidatetiles:")
+            {
+                assert(firstLine.size() == static_cast<std::string::size_type>(length));
+                _docBroker->tileCache().invalidateTiles(firstLine);
+            }
+            else if (tokens[0] == "renderfont:")
+            {
+                std::string font;
+                if (tokens.count() < 2 ||
+                    !getTokenString(tokens[1], "font", font))
+                    assert(false);
+
+                assert(firstLine.size() < static_cast<std::string::size_type>(length));
+                _docBroker->tileCache().saveRendering(font, "font", buffer + firstLine.size() + 1, length - firstLine.size() - 1);
+            }
+        }
+
+        forwardToPeer(buffer, length);
+        return true;
+    }
+
+    if (_kind == Kind::ToPrisoner)
+    {
+        // Message from child process to be forwarded to client.
+
+        // I think we should never get here
+        Log::error(getName() + ": Unexpected request [" + tokens[0] + "].");
+        assert(false);
+    }
+    else if (tokens[0] == "takeedit")
+    {
+        _docBroker->takeEditLock(getId());
+        return true;
+    }
+    else if (tokens[0] == "load")
+    {
+        if (_docURL != "")
+        {
+            sendTextFrame("error: cmd=load kind=docalreadyloaded");
+            return false;
+        }
+        return loadDocument(buffer, length, tokens);
+    }
+    else if (tokens[0] != "canceltiles" &&
+             tokens[0] != "clientzoom" &&
+             tokens[0] != "clientvisiblearea" &&
+             tokens[0] != "commandvalues" &&
+             tokens[0] != "downloadas" &&
+             tokens[0] != "getchildid" &&
+             tokens[0] != "gettextselection" &&
+             tokens[0] != "paste" &&
+             tokens[0] != "insertfile" &&
+             tokens[0] != "key" &&
+             tokens[0] != "mouse" &&
+             tokens[0] != "partpagerectangles" &&
+             tokens[0] != "renderfont" &&
+             tokens[0] != "requestloksession" &&
+             tokens[0] != "resetselection" &&
+             tokens[0] != "saveas" &&
+             tokens[0] != "selectgraphic" &&
+             tokens[0] != "selecttext" &&
+             tokens[0] != "setclientpart" &&
+             tokens[0] != "setpage" &&
+             tokens[0] != "status" &&
+             tokens[0] != "tile" &&
+             tokens[0] != "tilecombine" &&
+             tokens[0] != "uno" &&
+             tokens[0] != "useractive" &&
+             tokens[0] != "userinactive")
+    {
+        sendTextFrame("error: cmd=" + tokens[0] + " kind=unknown");
+        return false;
+    }
+    else if (_docURL == "")
+    {
+        sendTextFrame("error: cmd=" + tokens[0] + " kind=nodocloaded");
+        return false;
+    }
+    else if (tokens[0] == "canceltiles")
+    {
+        if (!_peer.expired())
+            forwardToPeer(buffer, length);
+    }
+    else if (tokens[0] == "commandvalues")
+    {
+        return getCommandValues(buffer, length, tokens);
+    }
+    else if (tokens[0] == "partpagerectangles")
+    {
+        return getPartPageRectangles(buffer, length);
+    }
+    else if (tokens[0] == "renderfont")
+    {
+        sendFontRendering(buffer, length, tokens);
+    }
+    else if (tokens[0] == "status")
+    {
+        return getStatus(buffer, length);
+    }
+    else if (tokens[0] == "tile")
+    {
+        sendTile(buffer, length, tokens);
+    }
+    else if (tokens[0] == "tilecombine")
+    {
+        sendCombinedTiles(buffer, length, tokens);
+    }
+    else
+    {
+        // All other commands are such that they always require a
+        // LibreOfficeKitDocument session, i.e. need to be handled in
+        // a child process.
+
+        if (_peer.expired())
+        {
+            Log::trace("Dispatching child to handle [" + tokens[0] + "].");
+            dispatchChild();
+        }
+
+        // Allow 'downloadas' for all kinds of views irrespective of editlock
+        if (_kind == Kind::ToClient && !isEditLocked() && tokens[0] != "downloadas" &&
+            tokens[0] != "userinactive" && tokens[0] != "useractive")
+        {
+            std::string dummyFrame = "dummymsg";
+            forwardToPeer(dummyFrame.c_str(), dummyFrame.size());
+        }
+        else if (tokens[0] != "requestloksession")
+        {
+            forwardToPeer(buffer, length);
+        }
+    }
+    return true;
+}
+
+bool MasterProcessSession::loadDocument(const char* /*buffer*/, int /*length*/, StringTokenizer& tokens)
+{
+    if (tokens.count() < 2)
+    {
+        sendTextFrame("error: cmd=load kind=syntax");
+        return false;
+    }
+
+    try
+    {
+        std::string timestamp;
+        parseDocOptions(tokens, _loadPart, timestamp);
+
+        // Finally, wait for the Child to connect to Master,
+        // link the document in jail and dispatch load to child.
+        Log::trace("Dispatching child to handle [load].");
+        dispatchChild();
+
+        return true;
+    }
+    catch (const Poco::SyntaxException&)
+    {
+        sendTextFrame("error: cmd=load kind=uriinvalid");
+    }
+
+    return false;
+}
+
+bool MasterProcessSession::getStatus(const char *buffer, int length)
+{
+    const std::string status = _docBroker->tileCache().getTextFile("status.txt");
+    if (!status.empty())
+    {
+        sendTextFrame(status);
+
+        // And let clients know if they hold the edit lock.
+        std::string message = "editlock: ";
+        message += std::to_string(isEditLocked());
+        Log::debug("Forwarding [" + message + "] in response to status.");
+        sendTextFrame(message);
+
+        return true;
+    }
+
+    if (_peer.expired())
+    {
+        Log::trace("Dispatching child to handle [getStatus].");
+        dispatchChild();
+    }
+
+    forwardToPeer(buffer, length);
+    return true;
+}
+
+void MasterProcessSession::setEditLock(const bool value)
+{
+    // Update the sate and forward to child.
+    _bEditLock = value;
+    const auto msg = std::string("editlock: ") + (value ? "1" : "0");
+    forwardToPeer(msg.data(), msg.size());
+}
+
+bool MasterProcessSession::getCommandValues(const char *buffer, int length, StringTokenizer& tokens)
+{
+    std::string command;
+    if (tokens.count() != 2 || !getTokenString(tokens[1], "command", command))
+    {
+        sendTextFrame("error: cmd=commandvalues kind=syntax");
+        return false;
+    }
+
+    const std::string cmdValues = _docBroker->tileCache().getTextFile("cmdValues" + command + ".txt");
+    if (cmdValues.size() > 0)
+    {
+        sendTextFrame(cmdValues);
+        return true;
+    }
+
+    if (_peer.expired())
+        dispatchChild();
+    forwardToPeer(buffer, length);
+    return true;
+}
+
+bool MasterProcessSession::getPartPageRectangles(const char *buffer, int length)
+{
+    const std::string partPageRectangles = _docBroker->tileCache().getTextFile("partpagerectangles.txt");
+    if (partPageRectangles.size() > 0)
+    {
+        sendTextFrame(partPageRectangles);
+        return true;
+    }
+
+    if (_peer.expired())
+        dispatchChild();
+    forwardToPeer(buffer, length);
+    return true;
+}
+
+std::string MasterProcessSession::getSaveAs()
+{
+    const auto payload = _saveAsQueue.get();
+    return std::string(payload.data(), payload.size());
+}
+
+void MasterProcessSession::sendFontRendering(const char *buffer, int length, StringTokenizer& tokens)
+{
+    std::string font;
+    if (tokens.count() < 2 ||
+        !getTokenString(tokens[1], "font", font))
+    {
+        sendTextFrame("error: cmd=renderfont kind=syntax");
+        return;
+    }
+
+    const std::string response = "renderfont: " + Poco::cat(std::string(" "), tokens.begin() + 1, tokens.end()) + "\n";
+
+    std::vector<char> output;
+    output.resize(response.size());
+    std::memcpy(output.data(), response.data(), response.size());
+
+    std::unique_ptr<std::fstream> cachedRendering = _docBroker->tileCache().lookupRendering(font, "font");
+    if (cachedRendering && cachedRendering->is_open())
+    {
+        cachedRendering->seekg(0, std::ios_base::end);
+        size_t pos = output.size();
+        std::streamsize size = cachedRendering->tellg();
+        output.resize(pos + size);
+        cachedRendering->seekg(0, std::ios_base::beg);
+        cachedRendering->read(output.data() + pos, size);
+        cachedRendering->close();
+
+        sendBinaryFrame(output.data(), output.size());
+        return;
+    }
+
+    if (_peer.expired())
+        dispatchChild();
+    forwardToPeer(buffer, length);
+}
+
+void MasterProcessSession::sendTile(const char * /*buffer*/, int /*length*/, StringTokenizer& tokens)
+{
+    try
+    {
+        auto tileDesc = TileDesc::parse(tokens);
+        _docBroker->handleTileRequest(tileDesc, shared_from_this());
+    }
+    catch (const std::exception& exc)
+    {
+        Log::error(std::string("Failed to process tile command: ") + exc.what() + ".");
+        sendTextFrame("error: cmd=tile kind=invalid");
+    }
+}
+
+void MasterProcessSession::sendCombinedTiles(const char* /*buffer*/, int /*length*/, StringTokenizer& tokens)
+{
+    int part, pixelWidth, pixelHeight, tileWidth, tileHeight;
+    std::string tilePositionsX, tilePositionsY;
+    if (tokens.count() < 8 ||
+        !getTokenInteger(tokens[1], "part", part) ||
+        !getTokenInteger(tokens[2], "width", pixelWidth) ||
+        !getTokenInteger(tokens[3], "height", pixelHeight) ||
+        !getTokenString (tokens[4], "tileposx", tilePositionsX) ||
+        !getTokenString (tokens[5], "tileposy", tilePositionsY) ||
+        !getTokenInteger(tokens[6], "tilewidth", tileWidth) ||
+        !getTokenInteger(tokens[7], "tileheight", tileHeight))
+    {
+        sendTextFrame("error: cmd=tilecombine kind=syntax");
+        return;
+    }
+
+    if (part < 0 || pixelWidth <= 0 || pixelHeight <= 0 ||
+        tileWidth <= 0 || tileHeight <= 0 ||
+        tilePositionsX.empty() || tilePositionsY.empty())
+    {
+        sendTextFrame("error: cmd=tilecombine kind=invalid");
+        return;
+    }
+
+    std::string reqTimestamp;
+    size_t index = 8;
+    if (tokens.count() > index && tokens[index].find("timestamp") == 0)
+    {
+        getTokenString(tokens[index], "timestamp", reqTimestamp);
+        ++index;
+    }
+
+    int id = -1;
+    if (tokens.count() > index && tokens[index].find("id") == 0)
+    {
+        getTokenInteger(tokens[index], "id", id);
+        ++index;
+    }
+
+    StringTokenizer positionXtokens(tilePositionsX, ",", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
+    StringTokenizer positionYtokens(tilePositionsY, ",", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
+
+    size_t numberOfPositions = positionYtokens.count();
+
+    // check that number of positions for X and Y is the same
+    if (numberOfPositions != positionXtokens.count())
+    {
+        sendTextFrame("error: cmd=tilecombine kind=invalid");
+        return;
+    }
+
+    for (size_t i = 0; i < numberOfPositions; ++i)
+    {
+        int x = 0;
+        if (!stringToInteger(positionXtokens[i], x))
+        {
+            sendTextFrame("error: cmd=tilecombine kind=syntax");
+            return;
+        }
+
+        int y = 0;
+        if (!stringToInteger(positionYtokens[i], y))
+        {
+            sendTextFrame("error: cmd=tilecombine kind=syntax");
+            return;
+        }
+
+        const TileDesc tile(part, pixelWidth, pixelHeight, x, y, tileWidth, tileHeight);
+        _docBroker->handleTileRequest(tile, shared_from_this());
+    }
+}
+
+void MasterProcessSession::dispatchChild()
+{
+    std::ostringstream oss;
+    oss << "load";
+    oss << " url=" << _docBroker->getPublicUri().toString();
+    oss << " jail=" << _docBroker->getJailedUri().toString();
+
+    if (_loadPart >= 0)
+        oss << " part=" + std::to_string(_loadPart);
+
+    if (_haveDocPassword)
+        oss << " password=" << _docPassword;
+
+    if (!_docOptions.empty())
+        oss << " options=" << _docOptions;
+
+    const auto loadRequest = oss.str();
+    forwardToPeer(loadRequest.c_str(), loadRequest.size());
+}
+
+void MasterProcessSession::forwardToPeer(const char *buffer, int length)
+{
+    const auto message = getAbbreviatedMessage(buffer, length);
+
+    auto peer = _peer.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;
+    }
+
+    Log::trace(getName() + " -> " + peer->getName() + ": " + message);
+    peer->sendBinaryFrame(buffer, length);
+}
+
+bool MasterProcessSession::shutdownPeer(Poco::UInt16 statusCode, const std::string& message)
+{
+    auto peer = _peer.lock();
+    if (peer && !peer->isCloseFrame())
+    {
+        peer->_ws->shutdown(statusCode, message);
+    }
+    return peer != nullptr;
+}
+#endif
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/loolwsd/PrisonerSession.hpp b/loolwsd/PrisonerSession.hpp
new file mode 100644
index 0000000..9b8745f
--- /dev/null
+++ b/loolwsd/PrisonerSession.hpp
@@ -0,0 +1,114 @@
+/* -*- 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_PRISONERSESSION_HPP
+#define INCLUDED_PRISONERSESSION_HPP
+
+#include <time.h>
+
+#include <Poco/Random.h>
+
+#include "MasterProcessSession.hpp"
+#include "LOOLSession.hpp"
+#include "MessageQueue.hpp"
+
+class DocumentBroker;
+class ClientSession;
+
+class PrisonerSession final : public MasterProcessSession//, public std::enable_shared_from_this<PrisonerSession>
+{
+public:
+    using MasterProcessSession::MasterProcessSession;
+
+    //void setPeer(const std::shared_ptr<ClientSession>& peer) { _peer = peer; }
+
+private:
+
+    //std::weak_ptr<ClientSession> _peer;
+#if 0
+ public:
+    MasterProcessSession(const std::string& id,
+                         const Kind kind,
+                         std::shared_ptr<Poco::Net::WebSocket> ws,
+                         std::shared_ptr<DocumentBroker> docBroker,
+                         std::shared_ptr<BasicTileQueue> queue);
+    virtual ~MasterProcessSession();
+
+    virtual bool getStatus(const char *buffer, int length) override;
+
+    virtual bool getCommandValues(const char *buffer, int length, Poco::StringTokenizer& tokens) override;
+
+    virtual bool getPartPageRectangles(const char *buffer, int length) override;
+
+    /**
+     * Return the URL of the saved-as document when it's ready. If called
+     * before it's ready, the call blocks till then.
+     */
+    std::string getSaveAs();
+
+    std::shared_ptr<DocumentBroker> getDocumentBroker() const { return _docBroker; }
+
+    std::shared_ptr<BasicTileQueue> getQueue() const { return _queue; }
+
+    void setPeer(const std::shared_ptr<MasterProcessSession>& peer) { _peer = peer; }
+
+    void setEditLock(const bool value);
+    void markEditLock(const bool value) { _bEditLock = value; }
+    bool isEditLocked() const { return _bEditLock; }
+
+    bool shutdownPeer(Poco::UInt16 statusCode, const std::string& message);
+
+public:
+    // Raise this flag on ToClient from ToPrisoner to let ToClient know of load failures
+    bool _bLoadError = false;
+
+ protected:
+    virtual bool loadDocument(const char *buffer, int length, Poco::StringTokenizer& tokens) override;
+
+    virtual void sendTile(const char *buffer, int length, Poco::StringTokenizer& tokens);
+
+    virtual void sendCombinedTiles(const char *buffer, int length, Poco::StringTokenizer& tokens);
+
+    virtual void sendFontRendering(const char *buffer, int length, Poco::StringTokenizer& tokens) override;
+
+ private:
+    void dispatchChild();
+    void forwardToPeer(const char *buffer, int length);
+
+    // If _kind==ToPrisoner and the child process has started and completed its handshake with the
+    // parent process: Points to the WebSocketSession for the child process handling the document in
+    // question, if any.
+
+    // In the session to the child process, points to the LOOLSession for the LOOL client. This will
+    // obvious have to be rethought when we add collaboration and there can be several LOOL clients
+    // per document being edited (i.e., per child process).
+    std::weak_ptr<MasterProcessSession> _peer;
+
+    static
+    Poco::Path getJailPath(const std::string& childId);
+
+    virtual bool _handleInput(const char *buffer, int length) override;
+
+    int _curPart;
+    int _loadPart;
+    /// Kind::ToClient instances store URLs of completed 'save as' documents.
+    MessageQueue _saveAsQueue;
+    std::shared_ptr<DocumentBroker> _docBroker;
+    std::shared_ptr<BasicTileQueue> _queue;
+
+    // If this document holds the edit lock.
+    // An edit lock will only allow the current session to make edits,
+    // while other session opening the same document can only see
+    bool _bEditLock = false;
+#endif
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */


More information about the Libreoffice-commits mailing list