2016-03-27 14:22:24 -05:00
|
|
|
/* -*- 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/poll.h>
|
|
|
|
|
|
|
|
#include <cassert>
|
|
|
|
#include <cstdlib>
|
|
|
|
#include <cstring>
|
|
|
|
#include <iomanip>
|
|
|
|
#include <mutex>
|
|
|
|
#include <sstream>
|
|
|
|
#include <string>
|
|
|
|
|
2016-03-27 15:06:22 -05:00
|
|
|
#include <Poco/StringTokenizer.h>
|
|
|
|
#include <Poco/Net/HTTPServerResponse.h>
|
2016-03-27 14:22:24 -05:00
|
|
|
#include <Poco/Net/WebSocket.h>
|
2016-03-27 15:06:22 -05:00
|
|
|
#include <Poco/Net/NetException.h>
|
2016-03-27 14:22:24 -05:00
|
|
|
#include <Poco/Thread.h>
|
2016-04-10 12:13:05 -05:00
|
|
|
#include <Poco/URI.h>
|
2016-03-27 14:22:24 -05:00
|
|
|
|
|
|
|
#include "Common.hpp"
|
2016-03-27 15:06:22 -05:00
|
|
|
#include "LOOLProtocol.hpp"
|
2016-03-27 14:22:24 -05:00
|
|
|
#include "IoUtil.hpp"
|
|
|
|
#include "Util.hpp"
|
|
|
|
|
2016-04-04 06:21:04 -05:00
|
|
|
using Poco::Net::NetException;
|
2016-03-27 15:06:22 -05:00
|
|
|
using Poco::Net::WebSocket;
|
|
|
|
using Poco::Net::WebSocketException;
|
|
|
|
|
2016-04-04 06:21:04 -05:00
|
|
|
namespace IoUtil
|
|
|
|
{
|
|
|
|
|
2016-03-27 15:06:22 -05:00
|
|
|
// Synchronously process WebSocket requests and dispatch to handler.
|
|
|
|
// Handler returns false to end.
|
|
|
|
void SocketProcessor(std::shared_ptr<WebSocket> ws,
|
2016-04-03 09:31:46 -05:00
|
|
|
Poco::Net::HTTPResponse& response,
|
2016-03-27 15:06:22 -05:00
|
|
|
std::function<bool(const std::vector<char>&)> handler,
|
2016-04-07 12:03:26 -05:00
|
|
|
std::function<bool()> stopPredicate)
|
2016-03-27 15:06:22 -05:00
|
|
|
{
|
2016-04-07 11:52:53 -05:00
|
|
|
Log::info("SocketProcessor starting.");
|
2016-03-27 15:06:22 -05:00
|
|
|
|
|
|
|
// Timeout given is in microseconds.
|
2016-04-07 12:03:26 -05:00
|
|
|
const Poco::Timespan waitTime(POLL_TIMEOUT_MS * 1000);
|
2016-03-27 15:06:22 -05:00
|
|
|
try
|
|
|
|
{
|
|
|
|
ws->setReceiveTimeout(0);
|
|
|
|
|
|
|
|
int flags = 0;
|
|
|
|
int n = 0;
|
|
|
|
bool stop = false;
|
|
|
|
std::vector<char> payload(READ_BUFFER_SIZE * 100);
|
2016-04-09 14:41:32 -05:00
|
|
|
payload.resize(0);
|
2016-03-27 15:06:22 -05:00
|
|
|
|
|
|
|
for (;;)
|
|
|
|
{
|
|
|
|
stop = stopPredicate();
|
|
|
|
if (stop)
|
|
|
|
{
|
2016-04-07 11:52:53 -05:00
|
|
|
Log::info("Termination flagged. Finishing.");
|
2016-03-27 15:06:22 -05:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ws->poll(waitTime, Poco::Net::Socket::SELECT_READ))
|
|
|
|
{
|
|
|
|
// Wait some more.
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
payload.resize(payload.capacity());
|
|
|
|
n = ws->receiveFrame(payload.data(), payload.capacity(), flags);
|
2016-04-09 14:41:32 -05:00
|
|
|
payload.resize(n > 0 ? n : 0);
|
2016-03-27 15:06:22 -05:00
|
|
|
|
|
|
|
if ((flags & WebSocket::FRAME_OP_BITMASK) == WebSocket::FRAME_OP_PING)
|
|
|
|
{
|
|
|
|
// Echo back the ping payload as pong.
|
|
|
|
// Technically, we should send back a PONG control frame.
|
|
|
|
// However Firefox (probably) or Node.js (possibly) doesn't
|
|
|
|
// like that and closes the socket when we do.
|
|
|
|
// Echoing the payload as a normal frame works with Firefox.
|
|
|
|
ws->sendFrame(payload.data(), n /*, WebSocket::FRAME_OP_PONG*/);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else if ((flags & WebSocket::FRAME_OP_BITMASK) == WebSocket::FRAME_OP_PONG)
|
|
|
|
{
|
|
|
|
// In case we do send pings in the future.
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else if (n <= 0 || ((flags & WebSocket::FRAME_OP_BITMASK) == WebSocket::FRAME_OP_CLOSE))
|
|
|
|
{
|
2016-04-07 11:52:53 -05:00
|
|
|
Log::warn("Connection closed.");
|
2016-03-27 15:06:22 -05:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(n > 0);
|
|
|
|
|
|
|
|
const std::string firstLine = LOOLProtocol::getFirstLine(payload);
|
|
|
|
if ((flags & WebSocket::FrameFlags::FRAME_FLAG_FIN) != WebSocket::FrameFlags::FRAME_FLAG_FIN)
|
|
|
|
{
|
|
|
|
// One WS message split into multiple frames.
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
char buffer[READ_BUFFER_SIZE * 10];
|
|
|
|
n = ws->receiveFrame(buffer, sizeof(buffer), flags);
|
|
|
|
if (n <= 0 || (flags & WebSocket::FRAME_OP_BITMASK) == WebSocket::FRAME_OP_CLOSE)
|
|
|
|
{
|
2016-04-10 12:13:05 -05:00
|
|
|
Log::warn("Connection closed while reading multiframe message.");
|
2016-03-27 15:06:22 -05:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
payload.insert(payload.end(), buffer, buffer + n);
|
|
|
|
if ((flags & WebSocket::FrameFlags::FRAME_FLAG_FIN) == WebSocket::FrameFlags::FRAME_FLAG_FIN)
|
|
|
|
{
|
|
|
|
// No more frames.
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int size = 0;
|
|
|
|
Poco::StringTokenizer tokens(firstLine, " ", Poco::StringTokenizer::TOK_IGNORE_EMPTY | Poco::StringTokenizer::TOK_TRIM);
|
|
|
|
if (tokens.count() == 2 &&
|
|
|
|
tokens[0] == "nextmessage:" && LOOLProtocol::getTokenInteger(tokens[1], "size", size) && size > 0)
|
|
|
|
{
|
|
|
|
// Check if it is a "nextmessage:" and in that case read the large
|
|
|
|
// follow-up message separately, and handle that only.
|
|
|
|
payload.resize(size);
|
|
|
|
|
|
|
|
n = ws->receiveFrame(payload.data(), size, flags);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (n <= 0 || (flags & WebSocket::FRAME_OP_BITMASK) == WebSocket::FRAME_OP_CLOSE)
|
|
|
|
{
|
2016-04-07 11:52:53 -05:00
|
|
|
Log::warn("Connection closed.");
|
2016-03-27 15:06:22 -05:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (firstLine == "eof")
|
|
|
|
{
|
2016-04-07 11:52:53 -05:00
|
|
|
Log::info("Received EOF. Finishing.");
|
2016-03-27 15:06:22 -05:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Call the handler.
|
2016-04-09 14:41:32 -05:00
|
|
|
const auto success = handler(payload);
|
|
|
|
payload.resize(0);
|
|
|
|
|
|
|
|
if (!success)
|
2016-03-27 15:06:22 -05:00
|
|
|
{
|
2016-04-07 11:52:53 -05:00
|
|
|
Log::info("Socket handler flagged to finish.");
|
2016-03-27 15:06:22 -05:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-10 12:13:05 -05:00
|
|
|
Log::info() << "SocketProcessor finishing. TerminationFlag: " << stop
|
2016-04-09 14:41:32 -05:00
|
|
|
<< ", n: " << n
|
2016-03-27 15:06:22 -05:00
|
|
|
<< ", payload size: " << payload.size()
|
|
|
|
<< ", flags: " << std::hex << flags << Log::end;
|
2016-04-04 19:59:46 -05:00
|
|
|
if (payload.size() > 1)
|
2016-03-27 15:06:22 -05:00
|
|
|
{
|
2016-04-10 12:13:05 -05:00
|
|
|
std::string msg;
|
|
|
|
Poco::URI::encode(std::string(payload.data(), payload.size()), "", msg);
|
|
|
|
Log::warn("Last message (" + std::to_string(payload.size()) +
|
|
|
|
" bytes) will not be processed: [" + msg + "].");
|
2016-03-27 15:06:22 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (const WebSocketException& exc)
|
|
|
|
{
|
|
|
|
Log::error("SocketProcessor: WebSocketException: " + exc.message());
|
|
|
|
switch (exc.code())
|
|
|
|
{
|
|
|
|
case WebSocket::WS_ERR_HANDSHAKE_UNSUPPORTED_VERSION:
|
|
|
|
response.set("Sec-WebSocket-Version", WebSocket::WEBSOCKET_VERSION);
|
|
|
|
// fallthrough
|
|
|
|
case WebSocket::WS_ERR_NO_HANDSHAKE:
|
|
|
|
case WebSocket::WS_ERR_HANDSHAKE_NO_VERSION:
|
|
|
|
case WebSocket::WS_ERR_HANDSHAKE_NO_KEY:
|
|
|
|
response.setStatusAndReason(Poco::Net::HTTPResponse::HTTP_BAD_REQUEST);
|
|
|
|
response.setContentLength(0);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2016-04-09 14:41:32 -05:00
|
|
|
catch (const Poco::Exception& exc)
|
|
|
|
{
|
|
|
|
Log::error("SocketProcessor: Exception: " + exc.message());
|
|
|
|
}
|
|
|
|
catch (const std::exception& exc)
|
2016-04-04 06:21:04 -05:00
|
|
|
{
|
2016-04-09 14:41:32 -05:00
|
|
|
Log::error("SocketProcessor: std::exception: " + std::string(exc.what()));
|
2016-04-04 06:21:04 -05:00
|
|
|
}
|
2016-03-27 15:06:22 -05:00
|
|
|
|
2016-04-07 11:52:53 -05:00
|
|
|
Log::info("SocketProcessor finished.");
|
2016-03-27 15:06:22 -05:00
|
|
|
}
|
2016-03-27 14:22:24 -05:00
|
|
|
|
|
|
|
void shutdownWebSocket(std::shared_ptr<Poco::Net::WebSocket> ws)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
if (ws)
|
|
|
|
ws->shutdown();
|
|
|
|
}
|
2016-04-08 06:25:42 -05:00
|
|
|
catch (const Poco::Exception& exc)
|
2016-03-27 14:22:24 -05:00
|
|
|
{
|
2016-04-08 06:25:42 -05:00
|
|
|
Log::warn("Util::shutdownWebSocket: Exception: " + exc.displayText() + (exc.nested() ? " (" + exc.nested()->displayText() + ")" : ""));
|
2016-03-27 14:22:24 -05:00
|
|
|
}
|
2016-04-08 06:25:42 -05:00
|
|
|
|
2016-03-27 14:22:24 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
ssize_t writeFIFO(int pipe, const char* buffer, ssize_t size)
|
|
|
|
{
|
|
|
|
ssize_t count = 0;
|
|
|
|
while(true)
|
|
|
|
{
|
2016-03-31 03:01:21 -05:00
|
|
|
Log::trace("Writing to pipe. Data: [" + Util::formatLinesForLog(std::string(buffer, size)) + "].");
|
2016-03-28 15:07:02 -05:00
|
|
|
const auto bytes = write(pipe, buffer + count, size - count);
|
2016-03-27 14:22:24 -05:00
|
|
|
if (bytes < 0)
|
|
|
|
{
|
|
|
|
if (errno == EINTR || errno == EAGAIN)
|
|
|
|
continue;
|
|
|
|
|
2016-04-07 02:36:38 -05:00
|
|
|
Log::syserror("Failed to write to pipe. Data: [" + std::string(buffer, size) + "].");
|
2016-03-27 14:22:24 -05:00
|
|
|
count = -1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else if (count + bytes < size)
|
|
|
|
{
|
|
|
|
count += bytes;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
count += bytes;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
ssize_t readFIFO(int pipe, char* buffer, ssize_t size)
|
|
|
|
{
|
|
|
|
ssize_t bytes;
|
|
|
|
do
|
|
|
|
{
|
|
|
|
bytes = read(pipe, buffer, size);
|
|
|
|
}
|
|
|
|
while (bytes < 0 && errno == EINTR);
|
|
|
|
|
|
|
|
return bytes;
|
|
|
|
}
|
|
|
|
|
2016-03-28 13:22:18 -05:00
|
|
|
/// Reads a single line from a pipe.
|
|
|
|
/// Returns 0 for timeout, <0 for error, and >0 on success.
|
|
|
|
/// On success, line will contain the read message.
|
|
|
|
int PipeReader::readLine(std::string& line,
|
2016-04-11 03:28:10 -05:00
|
|
|
std::function<bool()> stopPredicate)
|
2016-03-27 14:22:24 -05:00
|
|
|
{
|
2016-03-28 13:22:18 -05:00
|
|
|
const char *endOfLine = static_cast<const char *>(std::memchr(_data.data(), '\n', _data.size()));
|
|
|
|
if (endOfLine != nullptr)
|
|
|
|
{
|
|
|
|
// We have a line cached, return it.
|
|
|
|
line += std::string(_data.data(), endOfLine);
|
|
|
|
_data.erase(0, endOfLine - _data.data() + 1); // Including the '\n'.
|
2016-03-28 15:07:02 -05:00
|
|
|
Log::trace() << "Read existing line from pipe: " << _name << ", line: ["
|
2016-03-31 03:01:21 -05:00
|
|
|
<< line << "], data: [" << Util::formatLinesForLog(_data) << "]." << Log::end;
|
2016-03-28 13:22:18 -05:00
|
|
|
return 1;
|
|
|
|
}
|
2016-03-27 14:22:24 -05:00
|
|
|
|
2016-03-28 13:22:18 -05:00
|
|
|
// Poll in short intervals to check for stop condition.
|
|
|
|
const auto pollTimeoutMs = 500;
|
2016-04-11 03:28:10 -05:00
|
|
|
auto maxPollCount = POLL_TIMEOUT_MS / pollTimeoutMs;
|
2016-03-28 13:22:18 -05:00
|
|
|
while (maxPollCount-- > 0)
|
2016-03-27 14:22:24 -05:00
|
|
|
{
|
2016-03-28 13:22:18 -05:00
|
|
|
if (stopPredicate())
|
|
|
|
{
|
2016-03-28 15:07:02 -05:00
|
|
|
Log::info() << "Stop requested for pipe: " << _name << '.' << Log::end;
|
2016-03-28 13:22:18 -05:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct pollfd pipe;
|
|
|
|
pipe.fd = _pipe;
|
|
|
|
pipe.events = POLLIN;
|
|
|
|
pipe.revents = 0;
|
|
|
|
const int ready = poll(&pipe, 1, pollTimeoutMs);
|
|
|
|
if (ready == 0)
|
|
|
|
{
|
|
|
|
// Timeout.
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else if (ready < 0)
|
|
|
|
{
|
|
|
|
// error.
|
|
|
|
return ready;
|
|
|
|
}
|
|
|
|
else if (pipe.revents & (POLLIN | POLLPRI))
|
2016-03-27 14:22:24 -05:00
|
|
|
{
|
2016-03-28 13:22:18 -05:00
|
|
|
char buffer[READ_BUFFER_SIZE];
|
|
|
|
const auto bytes = readFIFO(_pipe, buffer, sizeof(buffer));
|
2016-03-28 15:07:02 -05:00
|
|
|
Log::trace() << "readFIFO for pipe: " << _name << " returned: " << bytes << Log::end;
|
2016-03-28 13:22:18 -05:00
|
|
|
if (bytes < 0)
|
2016-03-27 14:22:24 -05:00
|
|
|
{
|
2016-03-28 13:22:18 -05:00
|
|
|
return -1;
|
2016-03-27 14:22:24 -05:00
|
|
|
}
|
2016-03-28 13:22:18 -05:00
|
|
|
|
2016-03-30 02:39:48 -05:00
|
|
|
endOfLine = static_cast<const char *>(std::memchr(buffer, '\n', bytes));
|
2016-03-28 13:22:18 -05:00
|
|
|
if (endOfLine != nullptr)
|
2016-03-27 14:22:24 -05:00
|
|
|
{
|
2016-03-28 13:22:18 -05:00
|
|
|
// Got end of line.
|
|
|
|
line = _data;
|
2016-03-28 15:07:02 -05:00
|
|
|
const auto tail = std::string(static_cast<const char*>(buffer), endOfLine);
|
2016-03-28 13:22:18 -05:00
|
|
|
line += tail;
|
2016-03-28 15:07:02 -05:00
|
|
|
_data = std::string(endOfLine + 1, bytes - tail.size() - 1); // Exclude the '\n'.
|
|
|
|
Log::trace() << "Read line from pipe: " << _name << ", line: [" << line
|
2016-03-31 03:01:21 -05:00
|
|
|
<< "], data: [" << Util::formatLinesForLog(_data) << "]." << Log::end;
|
2016-03-28 13:22:18 -05:00
|
|
|
return 1;
|
2016-03-27 14:22:24 -05:00
|
|
|
}
|
2016-03-28 13:22:18 -05:00
|
|
|
else
|
2016-03-27 14:22:24 -05:00
|
|
|
{
|
2016-03-28 13:22:18 -05:00
|
|
|
// More data, keep going.
|
|
|
|
_data += std::string(buffer, bytes);
|
2016-03-28 15:07:02 -05:00
|
|
|
Log::trace() << "data appended to pipe: " << _name << ", data: " << _data << Log::end;
|
2016-03-27 14:22:24 -05:00
|
|
|
}
|
|
|
|
}
|
2016-03-28 13:22:18 -05:00
|
|
|
else if (pipe.revents & (POLLERR | POLLHUP | POLLNVAL))
|
2016-03-27 14:22:24 -05:00
|
|
|
{
|
2016-03-28 13:22:18 -05:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
2016-03-27 14:22:24 -05:00
|
|
|
|
2016-03-28 13:22:18 -05:00
|
|
|
// Timeout.
|
|
|
|
return 0;
|
|
|
|
}
|
2016-03-27 14:22:24 -05:00
|
|
|
|
2016-04-02 16:22:40 -05:00
|
|
|
bool PipeReader::processOnce(std::function<bool(std::string& message)> handler,
|
2016-04-11 03:28:10 -05:00
|
|
|
std::function<bool()> stopPredicate)
|
2016-04-02 16:22:40 -05:00
|
|
|
{
|
|
|
|
std::string line;
|
2016-04-11 03:28:10 -05:00
|
|
|
const auto ready = readLine(line, stopPredicate);
|
2016-04-02 16:22:40 -05:00
|
|
|
if (ready == 0)
|
|
|
|
{
|
|
|
|
// Timeout.
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if (ready < 0)
|
|
|
|
{
|
|
|
|
Log::error("Error reading from pipe [" + _name + "].");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else if (!handler(line))
|
|
|
|
{
|
|
|
|
Log::info("Pipe [" + _name + "] handler requested to finish.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-03-27 14:22:24 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|