2017-03-02 12:12:52 -06: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/.
|
|
|
|
*/
|
|
|
|
|
2017-12-20 07:06:26 -06:00
|
|
|
#include <config.h>
|
2017-03-08 10:38:22 -06:00
|
|
|
|
2017-03-06 15:26:53 -06:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <ctype.h>
|
2017-03-18 09:59:09 -05:00
|
|
|
#include <iomanip>
|
2017-03-19 10:45:29 -05:00
|
|
|
#include <zlib.h>
|
2017-03-30 06:08:47 -05:00
|
|
|
|
2017-03-15 13:21:59 -05:00
|
|
|
#include <Poco/DateTime.h>
|
|
|
|
#include <Poco/DateTimeFormat.h>
|
|
|
|
#include <Poco/DateTimeFormatter.h>
|
2017-04-23 20:25:09 -05:00
|
|
|
#include <Poco/Net/HTTPResponse.h>
|
2017-03-15 13:21:59 -05:00
|
|
|
|
2017-12-20 07:06:26 -06:00
|
|
|
#include <SigUtil.hpp>
|
2017-03-02 12:12:52 -06:00
|
|
|
#include "Socket.hpp"
|
2017-03-06 09:45:34 -06:00
|
|
|
#include "ServerSocket.hpp"
|
2017-03-18 09:59:09 -05:00
|
|
|
#include "WebSocketHandler.hpp"
|
2017-03-02 12:12:52 -06:00
|
|
|
|
2017-03-09 14:35:04 -06:00
|
|
|
int SocketPoll::DefaultPollTimeoutMs = 5000;
|
2017-05-14 20:42:10 -05:00
|
|
|
std::atomic<bool> SocketPoll::InhibitThreadChecks(false);
|
2017-04-05 11:58:52 -05:00
|
|
|
std::atomic<bool> Socket::InhibitThreadChecks(false);
|
2017-03-09 14:35:04 -06:00
|
|
|
|
2018-01-09 08:02:02 -06:00
|
|
|
int Socket::createSocket(Socket::Type type)
|
|
|
|
{
|
|
|
|
int domain = type == Type::IPv4 ? AF_INET : AF_INET6;
|
|
|
|
return socket(domain, SOCK_STREAM | SOCK_NONBLOCK, 0);
|
|
|
|
}
|
|
|
|
|
2017-03-03 15:18:27 -06:00
|
|
|
// help with initialization order
|
|
|
|
namespace {
|
|
|
|
std::vector<int> &getWakeupsArray()
|
|
|
|
{
|
|
|
|
static std::vector<int> pollWakeups;
|
|
|
|
return pollWakeups;
|
|
|
|
}
|
|
|
|
std::mutex &getPollWakeupsMutex()
|
|
|
|
{
|
|
|
|
static std::mutex pollWakeupsMutex;
|
|
|
|
return pollWakeupsMutex;
|
|
|
|
}
|
|
|
|
}
|
2017-03-02 12:12:52 -06:00
|
|
|
|
2017-03-09 13:00:04 -06:00
|
|
|
SocketPoll::SocketPoll(const std::string& threadName)
|
|
|
|
: _name(threadName),
|
2017-03-09 13:23:21 -06:00
|
|
|
_stop(false),
|
2017-03-12 21:11:25 -05:00
|
|
|
_threadStarted(false),
|
2017-04-02 16:49:14 -05:00
|
|
|
_threadFinished(false),
|
|
|
|
_owner(std::this_thread::get_id())
|
2017-03-02 12:12:52 -06:00
|
|
|
{
|
|
|
|
// Create the wakeup fd.
|
|
|
|
if (::pipe2(_wakeup, O_CLOEXEC | O_NONBLOCK) == -1)
|
|
|
|
{
|
2017-03-28 00:07:07 -05:00
|
|
|
throw std::runtime_error("Failed to allocate pipe for SocketPoll [" + threadName + "] waking.");
|
2017-03-02 12:12:52 -06:00
|
|
|
}
|
2017-03-06 22:09:09 -06:00
|
|
|
|
2017-04-02 17:43:21 -05:00
|
|
|
std::lock_guard<std::mutex> lock(getPollWakeupsMutex());
|
|
|
|
getWakeupsArray().push_back(_wakeup[1]);
|
2017-03-02 12:12:52 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
SocketPoll::~SocketPoll()
|
|
|
|
{
|
2017-03-31 11:28:20 -05:00
|
|
|
joinThread();
|
2017-03-06 22:09:09 -06:00
|
|
|
|
2017-03-17 13:56:59 -05:00
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock(getPollWakeupsMutex());
|
|
|
|
auto it = std::find(getWakeupsArray().begin(),
|
|
|
|
getWakeupsArray().end(),
|
|
|
|
_wakeup[1]);
|
2017-03-02 12:12:52 -06:00
|
|
|
|
2017-03-17 13:56:59 -05:00
|
|
|
if (it != getWakeupsArray().end())
|
|
|
|
getWakeupsArray().erase(it);
|
|
|
|
}
|
2017-03-03 15:18:27 -06:00
|
|
|
|
2017-03-17 13:56:59 -05:00
|
|
|
::close(_wakeup[0]);
|
|
|
|
::close(_wakeup[1]);
|
|
|
|
_wakeup[0] = -1;
|
|
|
|
_wakeup[1] = -1;
|
2017-03-03 15:18:27 -06:00
|
|
|
}
|
|
|
|
|
2017-03-09 13:00:04 -06:00
|
|
|
void SocketPoll::startThread()
|
|
|
|
{
|
2017-03-09 13:23:21 -06:00
|
|
|
if (!_threadStarted)
|
|
|
|
{
|
2017-03-19 12:22:12 -05:00
|
|
|
_threadStarted = true;
|
|
|
|
try
|
|
|
|
{
|
|
|
|
_thread = std::thread(&SocketPoll::pollingThreadEntry, this);
|
|
|
|
}
|
|
|
|
catch (const std::exception& exc)
|
|
|
|
{
|
|
|
|
LOG_ERR("Failed to start poll thread: " << exc.what());
|
|
|
|
_threadStarted = false;
|
|
|
|
}
|
2017-03-09 13:23:21 -06:00
|
|
|
}
|
2017-03-09 13:00:04 -06:00
|
|
|
}
|
|
|
|
|
2017-03-31 11:28:20 -05:00
|
|
|
void SocketPoll::joinThread()
|
|
|
|
{
|
2017-04-06 01:56:21 -05:00
|
|
|
addCallback([this](){ removeSockets(); });
|
2017-03-31 11:28:20 -05:00
|
|
|
stop();
|
|
|
|
if (_threadStarted && _thread.joinable())
|
|
|
|
{
|
|
|
|
if (_thread.get_id() == std::this_thread::get_id())
|
|
|
|
LOG_ERR("DEADLOCK PREVENTED: joining own thread!");
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_thread.join();
|
|
|
|
_threadStarted = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-03 15:18:27 -06:00
|
|
|
void SocketPoll::wakeupWorld()
|
|
|
|
{
|
|
|
|
for (const auto& fd : getWakeupsArray())
|
|
|
|
wakeup(fd);
|
2017-03-02 12:12:52 -06:00
|
|
|
}
|
|
|
|
|
2017-03-12 18:03:45 -05:00
|
|
|
void ServerSocket::dumpState(std::ostream& os)
|
2017-03-06 09:45:34 -06:00
|
|
|
{
|
2017-03-12 18:03:45 -05:00
|
|
|
os << "\t" << getFD() << "\t<accept>\n";
|
2017-03-06 09:45:34 -06:00
|
|
|
}
|
|
|
|
|
2017-05-05 05:51:43 -05:00
|
|
|
|
|
|
|
void SocketDisposition::execute()
|
|
|
|
{
|
|
|
|
// We should have hard ownership of this socket.
|
|
|
|
assert(_socket->getThreadOwner() == std::this_thread::get_id());
|
|
|
|
if (_socketMove)
|
2017-05-08 09:23:49 -05:00
|
|
|
{
|
|
|
|
// Drop pretentions of ownership before _socketMove.
|
|
|
|
_socket->setThreadOwner(std::thread::id(0));
|
2017-05-05 05:51:43 -05:00
|
|
|
_socketMove(_socket);
|
2017-05-08 09:23:49 -05:00
|
|
|
}
|
2017-05-05 05:51:43 -05:00
|
|
|
_socketMove = nullptr;
|
|
|
|
}
|
|
|
|
|
2017-03-18 09:59:09 -05:00
|
|
|
void WebSocketHandler::dumpState(std::ostream& os)
|
|
|
|
{
|
|
|
|
os << (_shuttingDown ? "shutd " : "alive ")
|
2018-02-11 18:14:21 -06:00
|
|
|
<< std::setw(5) << _pingTimeUs/1000. << "ms ";
|
2017-03-18 09:59:09 -05:00
|
|
|
if (_wsPayload.size() > 0)
|
2017-11-28 21:23:45 -06:00
|
|
|
Util::dumpHex(os, "\t\tws queued payload:\n", "\t\t", _wsPayload);
|
2017-05-26 09:50:08 -05:00
|
|
|
os << "\n";
|
2017-03-18 09:59:09 -05:00
|
|
|
}
|
|
|
|
|
2017-03-12 18:03:45 -05:00
|
|
|
void StreamSocket::dumpState(std::ostream& os)
|
2017-03-06 09:45:34 -06:00
|
|
|
{
|
2017-03-17 16:59:09 -05:00
|
|
|
int timeoutMaxMs = SocketPoll::DefaultPollTimeoutMs;
|
|
|
|
int events = getPollEvents(std::chrono::steady_clock::now(), timeoutMaxMs);
|
|
|
|
os << "\t" << getFD() << "\t" << events << "\t"
|
2017-05-26 09:50:08 -05:00
|
|
|
<< _inBuffer.size() << "\t" << _outBuffer.size() << "\t"
|
|
|
|
<< " r: " << _bytesRecvd << "\t w: " << _bytesSent << "\t";
|
2017-03-18 09:59:09 -05:00
|
|
|
_socketHandler->dumpState(os);
|
2017-03-06 15:26:53 -06:00
|
|
|
if (_inBuffer.size() > 0)
|
2017-11-28 21:23:45 -06:00
|
|
|
Util::dumpHex(os, "\t\tinBuffer:\n", "\t\t", _inBuffer);
|
2017-03-06 15:26:53 -06:00
|
|
|
if (_outBuffer.size() > 0)
|
2017-11-28 21:23:45 -06:00
|
|
|
Util::dumpHex(os, "\t\toutBuffer:\n", "\t\t", _inBuffer);
|
2017-03-06 09:45:34 -06:00
|
|
|
}
|
|
|
|
|
2017-04-23 20:25:09 -05:00
|
|
|
void StreamSocket::send(Poco::Net::HTTPResponse& response)
|
|
|
|
{
|
|
|
|
response.set("User-Agent", HTTP_AGENT_STRING);
|
|
|
|
response.set("Date", Poco::DateTimeFormatter::format(Poco::Timestamp(), Poco::DateTimeFormat::HTTP_FORMAT));
|
|
|
|
|
|
|
|
std::ostringstream oss;
|
|
|
|
response.write(oss);
|
|
|
|
|
|
|
|
send(oss.str());
|
|
|
|
}
|
|
|
|
|
2017-03-12 18:03:45 -05:00
|
|
|
void SocketPoll::dumpState(std::ostream& os)
|
2017-03-03 15:18:55 -06:00
|
|
|
{
|
2017-03-12 18:03:45 -05:00
|
|
|
// FIXME: NOT thread-safe! _pollSockets is modified from the polling thread!
|
|
|
|
os << " Poll [" << _pollSockets.size() << "] - wakeup r: "
|
|
|
|
<< _wakeup[0] << " w: " << _wakeup[1] << "\n";
|
2017-03-20 12:18:41 -05:00
|
|
|
if (_newCallbacks.size() > 0)
|
|
|
|
os << "\tcallbacks: " << _newCallbacks.size() << "\n";
|
2017-03-12 18:03:45 -05:00
|
|
|
os << "\tfd\tevents\trsize\twsize\n";
|
2017-03-03 15:18:55 -06:00
|
|
|
for (auto &i : _pollSockets)
|
2017-03-12 18:03:45 -05:00
|
|
|
i->dumpState(os);
|
2017-03-03 15:18:55 -06:00
|
|
|
}
|
|
|
|
|
2018-01-09 08:02:02 -06:00
|
|
|
/// Returns true on success only.
|
|
|
|
bool ServerSocket::bind(Type type, int port)
|
|
|
|
{
|
|
|
|
// Enable address reuse to avoid stalling after
|
|
|
|
// recycling, when previous socket is TIME_WAIT.
|
|
|
|
//TODO: Might be worth refactoring out.
|
|
|
|
const int reuseAddress = 1;
|
|
|
|
constexpr unsigned int len = sizeof(reuseAddress);
|
|
|
|
::setsockopt(getFD(), SOL_SOCKET, SO_REUSEADDR, &reuseAddress, len);
|
|
|
|
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
if (_type == Socket::Type::IPv4)
|
|
|
|
{
|
|
|
|
struct sockaddr_in addrv4;
|
|
|
|
std::memset(&addrv4, 0, sizeof(addrv4));
|
|
|
|
addrv4.sin_family = AF_INET;
|
|
|
|
addrv4.sin_port = htons(port);
|
|
|
|
if (type == Type::Public)
|
|
|
|
addrv4.sin_addr.s_addr = type == htonl(INADDR_ANY);
|
|
|
|
else
|
|
|
|
addrv4.sin_addr.s_addr = type == htonl(INADDR_LOOPBACK);
|
|
|
|
|
|
|
|
rc = ::bind(getFD(), (const sockaddr *)&addrv4, sizeof(addrv4));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
struct sockaddr_in6 addrv6;
|
|
|
|
std::memset(&addrv6, 0, sizeof(addrv6));
|
|
|
|
addrv6.sin6_family = AF_INET6;
|
|
|
|
addrv6.sin6_port = htons(port);
|
|
|
|
if (type == Type::Public)
|
|
|
|
addrv6.sin6_addr = in6addr_any;
|
|
|
|
else
|
|
|
|
addrv6.sin6_addr = in6addr_loopback;
|
|
|
|
|
|
|
|
int ipv6only = _type == Socket::Type::All ? 0 : 1;
|
|
|
|
if (::setsockopt(getFD(), IPPROTO_IPV6, IPV6_V6ONLY, (char*)&ipv6only, sizeof(ipv6only)) == -1)
|
|
|
|
LOG_SYS("Failed set ipv6 socket to %d" << ipv6only);
|
|
|
|
|
|
|
|
rc = ::bind(getFD(), (const sockaddr *)&addrv6, sizeof(addrv6));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rc)
|
|
|
|
LOG_SYS("Failed to bind to: " << (_type == Socket::Type::IPv4 ? "IPv4" : "IPv6") << " port: " << port);
|
|
|
|
|
|
|
|
return rc == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-03-15 13:21:59 -05:00
|
|
|
namespace HttpHelper
|
|
|
|
{
|
2017-04-22 20:10:54 -05:00
|
|
|
void sendUncompressedFileContent(const std::shared_ptr<StreamSocket>& socket,
|
|
|
|
const std::string& path,
|
|
|
|
const int bufferSize)
|
|
|
|
{
|
|
|
|
std::ifstream file(path, std::ios::binary);
|
|
|
|
std::unique_ptr<char[]> buf(new char[bufferSize]);
|
|
|
|
do
|
|
|
|
{
|
|
|
|
file.read(&buf[0], bufferSize);
|
|
|
|
const int size = file.gcount();
|
|
|
|
if (size > 0)
|
|
|
|
socket->send(&buf[0], size, true);
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
while (file);
|
|
|
|
}
|
|
|
|
|
|
|
|
void sendDeflatedFileContent(const std::shared_ptr<StreamSocket>& socket,
|
|
|
|
const std::string& path,
|
|
|
|
const int fileSize)
|
|
|
|
{
|
|
|
|
// FIXME: Should compress once ahead of time
|
|
|
|
// compression of bundle.js takes significant time:
|
|
|
|
// 200's ms for level 9 (468k), 72ms for level 1(587k)
|
|
|
|
// down from 2Mb.
|
|
|
|
if (fileSize > 0)
|
|
|
|
{
|
|
|
|
std::ifstream file(path, std::ios::binary);
|
|
|
|
std::unique_ptr<char[]> buf(new char[fileSize]);
|
|
|
|
file.read(&buf[0], fileSize);
|
|
|
|
|
|
|
|
static const unsigned int Level = 1;
|
|
|
|
const long unsigned int size = file.gcount();
|
|
|
|
long unsigned int compSize = compressBound(size);
|
|
|
|
std::unique_ptr<char[]> cbuf(new char[compSize]);
|
|
|
|
compress2((Bytef *)&cbuf[0], &compSize, (Bytef *)&buf[0], size, Level);
|
|
|
|
|
|
|
|
if (size > 0)
|
|
|
|
socket->send(&cbuf[0], compSize, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void sendFile(const std::shared_ptr<StreamSocket>& socket,
|
|
|
|
const std::string& path,
|
|
|
|
const std::string& mediaType,
|
|
|
|
Poco::Net::HTTPResponse& response,
|
|
|
|
const bool noCache,
|
|
|
|
const bool deflate,
|
|
|
|
const bool headerOnly)
|
2017-03-15 13:21:59 -05:00
|
|
|
{
|
|
|
|
struct stat st;
|
|
|
|
if (stat(path.c_str(), &st) != 0)
|
|
|
|
{
|
|
|
|
LOG_WRN("#" << socket->getFD() << ": Failed to stat [" << path << "]. File will not be sent.");
|
|
|
|
throw Poco::FileNotFoundException("Failed to stat [" + path + "]. File will not be sent.");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!noCache)
|
|
|
|
{
|
|
|
|
// 60 * 60 * 24 * 128 (days) = 11059200
|
|
|
|
response.set("Cache-Control", "max-age=11059200");
|
|
|
|
response.set("ETag", "\"" LOOLWSD_VERSION_HASH "\"");
|
|
|
|
}
|
|
|
|
|
2017-04-09 10:48:05 -05:00
|
|
|
response.setContentType(mediaType);
|
|
|
|
response.add("X-Content-Type-Options", "nosniff");
|
|
|
|
|
2017-04-03 22:57:59 -05:00
|
|
|
int bufferSize = std::min(st.st_size, (off_t)Socket::MaximumSendBufferSize);
|
|
|
|
if (st.st_size >= socket->getSendBufferSize())
|
2017-03-19 10:45:29 -05:00
|
|
|
{
|
2017-04-03 22:57:59 -05:00
|
|
|
socket->setSocketBufferSize(bufferSize);
|
|
|
|
bufferSize = socket->getSendBufferSize();
|
|
|
|
}
|
2017-03-19 10:45:29 -05:00
|
|
|
|
2017-04-04 07:17:23 -05:00
|
|
|
// Disable deflate for now - until we can cache deflated data.
|
2017-04-06 10:07:40 -05:00
|
|
|
// FIXME: IE/Edge doesn't work well with deflate, so check with
|
|
|
|
// IE/Edge before enabling the deflate again
|
|
|
|
if (!deflate || true)
|
2017-04-03 22:57:59 -05:00
|
|
|
{
|
2017-03-19 10:45:29 -05:00
|
|
|
response.setContentLength(st.st_size);
|
2017-04-22 20:10:54 -05:00
|
|
|
LOG_TRC("#" << socket->getFD() << ": Sending " <<
|
|
|
|
(headerOnly ? "header for " : "") << " file [" << path << "].");
|
|
|
|
socket->send(response);
|
2017-03-15 13:21:59 -05:00
|
|
|
|
2017-04-22 20:10:54 -05:00
|
|
|
if (!headerOnly)
|
|
|
|
sendUncompressedFileContent(socket, path, bufferSize);
|
2017-03-19 10:45:29 -05:00
|
|
|
}
|
|
|
|
else
|
2017-03-15 13:21:59 -05:00
|
|
|
{
|
2017-03-19 10:45:29 -05:00
|
|
|
response.set("Content-Encoding", "deflate");
|
2017-04-22 20:10:54 -05:00
|
|
|
LOG_TRC("#" << socket->getFD() << ": Sending " <<
|
|
|
|
(headerOnly ? "header for " : "") << " file [" << path << "].");
|
|
|
|
socket->send(response);
|
2017-03-19 10:45:29 -05:00
|
|
|
|
2017-04-22 20:10:54 -05:00
|
|
|
if (!headerOnly)
|
|
|
|
sendDeflatedFileContent(socket, path, st.st_size);
|
2017-03-15 13:21:59 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-02 12:12:52 -06:00
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|