libreoffice-online/loolwsd/LOOLSession.cpp
Pranav Kant 9ea4624839 loolwsd: Handle socket exceptions while trying to send frame
In case of abnormal termination of session from client-side,
we might still have data being processed in the kit process, for
example, during auto-save. Trying to send such data over an
expired socket towards the client would throw exceptions which
need to be handled, otherwise the auto-save process would not be
successful. Further, the unhandled exception can bring the document
broker in an unstable state with dockey still present in the
object.

Also do not send 'editlock: 0' to a websocket session which is
going to be removed because the session might have been
forcefully terminated from the client-side, in which case sending
data would go nowhere.

Change-Id: I10eb9c818bc81d4db26d5a19dc8bd44f6fbdf32c
2016-04-13 09:56:32 +05:30

240 lines
6.7 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/.
*/
#include <sys/stat.h>
#include <sys/types.h>
#include <ftw.h>
#include <utime.h>
#include <cassert>
#include <cstring>
#include <fstream>
#include <iostream>
#include <iterator>
#include <map>
#include <memory>
#include <mutex>
#include <set>
#include <Poco/Exception.h>
#include <Poco/Net/Socket.h>
#include <Poco/Net/WebSocket.h>
#include <Poco/Path.h>
#include <Poco/String.h>
#include <Poco/StringTokenizer.h>
#include "Common.hpp"
#include "LOOLProtocol.hpp"
#include "LOOLSession.hpp"
#include "TileCache.hpp"
#include "IoUtil.hpp"
#include "Util.hpp"
using namespace LOOLProtocol;
using Poco::Exception;
using Poco::IOException;
using Poco::Net::Socket;
using Poco::Net::WebSocket;
using Poco::Path;
using Poco::StringTokenizer;
LOOLSession::LOOLSession(const std::string& id, const Kind kind,
std::shared_ptr<WebSocket> ws) :
_kind(kind),
_kindString(kind == Kind::ToClient ? "ToClient" :
kind == Kind::ToMaster ? "ToMaster" : "ToPrisoner"),
_ws(ws),
_docPassword(""),
_isDocPasswordProvided(false),
_isDocLoaded(false),
_isDocPasswordProtected(false),
_disconnected(false),
_lastActivityTime(std::chrono::steady_clock::now())
{
// Only a post request can have a null ws.
if (_kind != Kind::ToClient)
assert(_ws);
setId(id);
}
LOOLSession::~LOOLSession()
{
IoUtil::shutdownWebSocket(_ws);
}
void LOOLSession::sendTextFrame(const std::string& text)
{
if (!_ws || _ws->poll(Poco::Timespan(0), Socket::SelectMode::SELECT_ERROR))
{
Log::error("Socket error to send " + getAbbreviatedMessage(text.c_str(), text.size()) + " to.");
return;
}
else
Log::trace(getName() + " Send: " + getAbbreviatedMessage(text.c_str(), text.size()));
std::unique_lock<std::mutex> lock(_mutex);
try
{
const int length = text.size();
if ( length > SMALL_MESSAGE_SIZE )
{
const std::string nextmessage = "nextmessage: size=" + std::to_string(length);
_ws->sendFrame(nextmessage.data(), nextmessage.size());
}
_ws->sendFrame(text.data(), length);
}
catch (const Exception& exc)
{
Log::warn() << "LOOLSession::sendTextFrame: "
<< "Exception: " << exc.displayText()
<< (exc.nested() ? "( " + exc.nested()->displayText() + ")" : "");
IoUtil::shutdownWebSocket(_ws);
}
}
void LOOLSession::sendBinaryFrame(const char *buffer, int length)
{
if (!_ws || _ws->poll(Poco::Timespan(0), Socket::SelectMode::SELECT_ERROR))
{
Log::error("Socket error to send binary frame of " + std::to_string(length) + " bytes to.");
return;
}
else
Log::trace(getName() + " Send: " + std::to_string(length) + " bytes");
std::unique_lock<std::mutex> lock(_mutex);
try
{
if ( length > SMALL_MESSAGE_SIZE )
{
const std::string nextmessage = "nextmessage: size=" + std::to_string(length);
_ws->sendFrame(nextmessage.data(), nextmessage.size());
}
_ws->sendFrame(buffer, length, WebSocket::FRAME_BINARY);
}
catch (const Exception& exc)
{
Log::warn() << "LOOLSession::sendBinaryFrame: "
<< "Exception: " << exc.displayText()
<< (exc.nested() ? "( " + exc.nested()->displayText() + ")" : "");
IoUtil::shutdownWebSocket(_ws);
}
}
void LOOLSession::parseDocOptions(const StringTokenizer& tokens, int& part, std::string& timestamp)
{
// First token is the "load" command itself.
size_t offset = 1;
if (tokens.count() > 2 && tokens[1].find("part=") == 0)
{
getTokenInteger(tokens[1], "part", part);
++offset;
}
for (size_t i = offset; i < tokens.count(); ++i)
{
if (tokens[i].find("url=") == 0)
{
_docURL = tokens[i].substr(strlen("url="));
++offset;
}
else if (tokens[i].find("jail=") == 0)
{
_jailedFilePath = tokens[i].substr(strlen("jail="));
++offset;
}
else if (tokens[i].find("timestamp=") == 0)
{
timestamp = tokens[i].substr(strlen("timestamp="));
++offset;
}
else if (tokens[i].find("password=") == 0)
{
_docPassword = tokens[i].substr(strlen("password="));
_isDocPasswordProvided = true;
++offset;
}
}
if (tokens.count() > offset)
{
if (getTokenString(tokens[offset], "options", _docOptions))
{
if (tokens.count() > offset + 1)
_docOptions += Poco::cat(std::string(" "), tokens.begin() + offset + 1, tokens.end());
}
}
}
void LOOLSession::disconnect(const std::string& reason)
{
try
{
if (!_disconnected)
{
if (reason != "")
sendTextFrame("disconnect " + reason);
else
sendTextFrame("disconnect");
_disconnected = true;
IoUtil::shutdownWebSocket(_ws);
}
}
catch (const IOException& exc)
{
Log::error("LOOLSession::disconnect: Exception: " + exc.displayText() + (exc.nested() ? " (" + exc.nested()->displayText() + ")" : ""));
}
}
bool LOOLSession::handleDisconnect(StringTokenizer& /*tokens*/)
{
_disconnected = true;
IoUtil::shutdownWebSocket(_ws);
return false;
}
bool LOOLSession::handleInput(const char *buffer, int length)
{
assert(buffer != nullptr);
const auto summary = getAbbreviatedMessage(buffer, length);
try
{
Log::trace(getName() + " Recv: " + summary);
if (TerminationFlag)
{
Log::warn("Input while terminating: [" + summary + "].");
}
return _handleInput(buffer, length);
}
catch (const Exception& exc)
{
Log::error() << "LOOLSession::handleInput: Exception while handling [" + summary + "] in "
<< getName() << ": "
<< exc.displayText()
<< (exc.nested() ? " (" + exc.nested()->displayText() + ")" : "")
<< Log::end;
}
catch (const std::exception& exc)
{
Log::error("LOOLSession::handleInput: Exception while handling [" + summary + "]: " + exc.what());
}
return false;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */