2015-04-13 04:09:02 -05:00
|
|
|
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
2015-03-04 17:14:04 -06:00
|
|
|
/*
|
|
|
|
* 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/.
|
|
|
|
*/
|
|
|
|
|
loolwsd: include cleanup and organization
A source file (.cpp) must include its own header first.
This insures that the header is self-contained and
doesn't depend on arbitrary (and accidental) includes
before it to compile.
Furthermore, system headers should go next, followed by
C then C++ headers, then libraries (Poco, etc) and, finally,
project headers come last.
This makes sure that headers and included in the same dependency
order to avoid side-effects. For example, Poco should never rely on
anything from our project in the same way that a C header should
never rely on anything in C++, Poco, or project headers.
Also, includes ought to be sorted where possible, to improve
readability and avoid accidental duplicates (of which there
were a few).
Change-Id: I62cc1343e4a091d69195e37ed659dba20cfcb1ef
Reviewed-on: https://gerrit.libreoffice.org/25262
Reviewed-by: Ashod Nakashian <ashnakash@gmail.com>
Tested-by: Ashod Nakashian <ashnakash@gmail.com>
2016-05-21 09:23:07 -05:00
|
|
|
#include "LOOLWSD.hpp"
|
2015-04-21 08:51:28 -05:00
|
|
|
#include "config.h"
|
|
|
|
|
2016-07-18 06:45:36 -05:00
|
|
|
/* Default host used in the start test URI */
|
|
|
|
#define LOOLWSD_TEST_HOST "localhost"
|
|
|
|
|
|
|
|
/* Default loleaflet UI used in the start test URI */
|
|
|
|
#define LOOLWSD_TEST_LOLEAFLET_UI "/loleaflet/" LOOLWSD_VERSION_HASH "/loleaflet.html"
|
|
|
|
|
|
|
|
/* Default document used in the start test URI */
|
2016-10-15 08:08:09 -05:00
|
|
|
#define LOOLWSD_TEST_DOCUMENT_RELATIVE_PATH "test/data/hello.odt"
|
2016-07-18 06:45:36 -05:00
|
|
|
|
2015-04-10 07:20:04 -05:00
|
|
|
// This is the main source for the loolwsd program. LOOL uses several loolwsd processes: one main
|
|
|
|
// parent process that listens on the TCP port and accepts connections from LOOL clients, and a
|
|
|
|
// number of child processes, each which handles a viewing (editing) session for one document.
|
|
|
|
|
2015-04-08 09:22:42 -05:00
|
|
|
#include <unistd.h>
|
|
|
|
|
2015-04-16 11:15:40 -05:00
|
|
|
#include <sys/types.h>
|
2016-04-04 01:36:27 -05:00
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/wait.h>
|
2015-04-16 11:15:40 -05:00
|
|
|
|
|
|
|
#include <cassert>
|
2016-08-31 09:02:29 -05:00
|
|
|
#include <cerrno>
|
|
|
|
#include <clocale>
|
2016-04-10 01:37:51 -05:00
|
|
|
#include <condition_variable>
|
2015-03-28 06:55:35 -05:00
|
|
|
#include <cstdlib>
|
2015-04-14 09:50:38 -05:00
|
|
|
#include <cstring>
|
2016-08-31 09:02:29 -05:00
|
|
|
#include <ctime>
|
loolwsd: include cleanup and organization
A source file (.cpp) must include its own header first.
This insures that the header is self-contained and
doesn't depend on arbitrary (and accidental) includes
before it to compile.
Furthermore, system headers should go next, followed by
C then C++ headers, then libraries (Poco, etc) and, finally,
project headers come last.
This makes sure that headers and included in the same dependency
order to avoid side-effects. For example, Poco should never rely on
anything from our project in the same way that a C header should
never rely on anything in C++, Poco, or project headers.
Also, includes ought to be sorted where possible, to improve
readability and avoid accidental duplicates (of which there
were a few).
Change-Id: I62cc1343e4a091d69195e37ed659dba20cfcb1ef
Reviewed-on: https://gerrit.libreoffice.org/25262
Reviewed-by: Ashod Nakashian <ashnakash@gmail.com>
Tested-by: Ashod Nakashian <ashnakash@gmail.com>
2016-05-21 09:23:07 -05:00
|
|
|
#include <fstream>
|
2015-03-04 17:14:04 -06:00
|
|
|
#include <iostream>
|
2016-03-22 13:27:38 -05:00
|
|
|
#include <map>
|
2015-07-13 09:13:06 -05:00
|
|
|
#include <mutex>
|
2016-03-08 01:31:29 -06:00
|
|
|
#include <sstream>
|
2016-04-14 10:22:19 -05:00
|
|
|
#include <thread>
|
2015-03-04 17:14:04 -06:00
|
|
|
|
2016-03-22 10:25:35 -05:00
|
|
|
#include <Poco/DOM/AutoPtr.h>
|
|
|
|
#include <Poco/DOM/DOMParser.h>
|
|
|
|
#include <Poco/DOM/DOMWriter.h>
|
|
|
|
#include <Poco/DOM/Document.h>
|
|
|
|
#include <Poco/DOM/Element.h>
|
|
|
|
#include <Poco/DOM/NodeList.h>
|
2016-10-07 11:49:37 -05:00
|
|
|
#include <Poco/Environment.h>
|
2015-05-08 07:46:10 -05:00
|
|
|
#include <Poco/Exception.h>
|
2015-04-16 11:15:40 -05:00
|
|
|
#include <Poco/File.h>
|
2016-02-24 01:39:23 -06:00
|
|
|
#include <Poco/FileStream.h>
|
2016-03-30 10:57:17 -05:00
|
|
|
#include <Poco/Net/AcceptCertificateHandler.h>
|
2016-03-21 03:37:39 -05:00
|
|
|
#include <Poco/Net/ConsoleCertificateHandler.h>
|
|
|
|
#include <Poco/Net/Context.h>
|
2015-10-16 10:38:24 -05:00
|
|
|
#include <Poco/Net/HTMLForm.h>
|
2015-03-04 17:14:04 -06:00
|
|
|
#include <Poco/Net/HTTPRequest.h>
|
|
|
|
#include <Poco/Net/HTTPRequestHandler.h>
|
|
|
|
#include <Poco/Net/HTTPRequestHandlerFactory.h>
|
|
|
|
#include <Poco/Net/HTTPServer.h>
|
|
|
|
#include <Poco/Net/HTTPServerParams.h>
|
|
|
|
#include <Poco/Net/HTTPServerRequest.h>
|
|
|
|
#include <Poco/Net/HTTPServerResponse.h>
|
2016-03-21 03:37:39 -05:00
|
|
|
#include <Poco/Net/InvalidCertificateHandler.h>
|
|
|
|
#include <Poco/Net/KeyConsoleHandler.h>
|
2015-10-16 10:38:24 -05:00
|
|
|
#include <Poco/Net/MessageHeader.h>
|
2016-07-19 11:40:17 -05:00
|
|
|
#include <Poco/Net/NameValueCollection.h>
|
2016-02-24 01:39:23 -06:00
|
|
|
#include <Poco/Net/Net.h>
|
2015-03-04 17:14:04 -06:00
|
|
|
#include <Poco/Net/NetException.h>
|
2015-10-16 10:38:24 -05:00
|
|
|
#include <Poco/Net/PartHandler.h>
|
2016-03-21 03:37:39 -05:00
|
|
|
#include <Poco/Net/PrivateKeyPassphraseHandler.h>
|
2016-03-22 10:25:35 -05:00
|
|
|
#include <Poco/Net/SSLManager.h>
|
2016-03-21 03:37:39 -05:00
|
|
|
#include <Poco/Net/SecureServerSocket.h>
|
2015-03-04 17:14:04 -06:00
|
|
|
#include <Poco/Net/ServerSocket.h>
|
2015-05-08 13:24:46 -05:00
|
|
|
#include <Poco/Net/SocketAddress.h>
|
2015-03-04 17:14:04 -06:00
|
|
|
#include <Poco/Net/WebSocket.h>
|
2015-04-16 11:15:40 -05:00
|
|
|
#include <Poco/Path.h>
|
2016-10-12 10:15:13 -05:00
|
|
|
#include <Poco/Pipe.h>
|
2015-03-27 11:23:27 -05:00
|
|
|
#include <Poco/Process.h>
|
2016-03-22 10:25:35 -05:00
|
|
|
#include <Poco/SAX/InputSource.h>
|
2016-02-24 01:39:23 -06:00
|
|
|
#include <Poco/StreamCopier.h>
|
2015-05-18 03:21:30 -05:00
|
|
|
#include <Poco/StringTokenizer.h>
|
2016-02-24 01:39:23 -06:00
|
|
|
#include <Poco/TemporaryFile.h>
|
2015-05-29 00:49:49 -05:00
|
|
|
#include <Poco/ThreadPool.h>
|
2016-02-24 01:39:23 -06:00
|
|
|
#include <Poco/URI.h>
|
2015-03-04 17:14:04 -06:00
|
|
|
#include <Poco/Util/HelpFormatter.h>
|
2016-06-25 07:20:56 -05:00
|
|
|
#include <Poco/Util/MapConfiguration.h>
|
2015-03-04 17:14:04 -06:00
|
|
|
#include <Poco/Util/Option.h>
|
2015-04-08 09:22:42 -05:00
|
|
|
#include <Poco/Util/OptionException.h>
|
2015-03-04 17:14:04 -06:00
|
|
|
#include <Poco/Util/OptionSet.h>
|
|
|
|
#include <Poco/Util/ServerApplication.h>
|
|
|
|
|
2016-02-15 17:05:24 -06:00
|
|
|
#include "Admin.hpp"
|
|
|
|
#include "Auth.hpp"
|
loolwsd: include cleanup and organization
A source file (.cpp) must include its own header first.
This insures that the header is self-contained and
doesn't depend on arbitrary (and accidental) includes
before it to compile.
Furthermore, system headers should go next, followed by
C then C++ headers, then libraries (Poco, etc) and, finally,
project headers come last.
This makes sure that headers and included in the same dependency
order to avoid side-effects. For example, Poco should never rely on
anything from our project in the same way that a C header should
never rely on anything in C++, Poco, or project headers.
Also, includes ought to be sorted where possible, to improve
readability and avoid accidental duplicates (of which there
were a few).
Change-Id: I62cc1343e4a091d69195e37ed659dba20cfcb1ef
Reviewed-on: https://gerrit.libreoffice.org/25262
Reviewed-by: Ashod Nakashian <ashnakash@gmail.com>
Tested-by: Ashod Nakashian <ashnakash@gmail.com>
2016-05-21 09:23:07 -05:00
|
|
|
#include "ClientSession.hpp"
|
2016-02-24 01:39:23 -06:00
|
|
|
#include "Common.hpp"
|
2016-04-16 11:56:23 -05:00
|
|
|
#include "Exceptions.hpp"
|
2016-03-20 05:59:32 -05:00
|
|
|
#include "FileServer.hpp"
|
2016-04-16 11:56:23 -05:00
|
|
|
#include "IoUtil.hpp"
|
2015-04-14 09:50:38 -05:00
|
|
|
#include "LOOLProtocol.hpp"
|
2015-03-09 03:01:30 -05:00
|
|
|
#include "LOOLSession.hpp"
|
loolwsd: include cleanup and organization
A source file (.cpp) must include its own header first.
This insures that the header is self-contained and
doesn't depend on arbitrary (and accidental) includes
before it to compile.
Furthermore, system headers should go next, followed by
C then C++ headers, then libraries (Poco, etc) and, finally,
project headers come last.
This makes sure that headers and included in the same dependency
order to avoid side-effects. For example, Poco should never rely on
anything from our project in the same way that a C header should
never rely on anything in C++, Poco, or project headers.
Also, includes ought to be sorted where possible, to improve
readability and avoid accidental duplicates (of which there
were a few).
Change-Id: I62cc1343e4a091d69195e37ed659dba20cfcb1ef
Reviewed-on: https://gerrit.libreoffice.org/25262
Reviewed-by: Ashod Nakashian <ashnakash@gmail.com>
Tested-by: Ashod Nakashian <ashnakash@gmail.com>
2016-05-21 09:23:07 -05:00
|
|
|
#include "Log.hpp"
|
2016-05-16 06:37:02 -05:00
|
|
|
#include "PrisonerSession.hpp"
|
2016-01-06 07:38:21 -06:00
|
|
|
#include "QueueHandler.hpp"
|
2016-03-25 21:56:18 -05:00
|
|
|
#include "Storage.hpp"
|
2016-08-17 18:57:38 -05:00
|
|
|
#include "TraceFile.hpp"
|
2016-04-05 11:41:10 -05:00
|
|
|
#include "Unit.hpp"
|
2016-04-07 15:59:27 -05:00
|
|
|
#include "UnitHTTP.hpp"
|
loolwsd: include cleanup and organization
A source file (.cpp) must include its own header first.
This insures that the header is self-contained and
doesn't depend on arbitrary (and accidental) includes
before it to compile.
Furthermore, system headers should go next, followed by
C then C++ headers, then libraries (Poco, etc) and, finally,
project headers come last.
This makes sure that headers and included in the same dependency
order to avoid side-effects. For example, Poco should never rely on
anything from our project in the same way that a C header should
never rely on anything in C++, Poco, or project headers.
Also, includes ought to be sorted where possible, to improve
readability and avoid accidental duplicates (of which there
were a few).
Change-Id: I62cc1343e4a091d69195e37ed659dba20cfcb1ef
Reviewed-on: https://gerrit.libreoffice.org/25262
Reviewed-by: Ashod Nakashian <ashnakash@gmail.com>
Tested-by: Ashod Nakashian <ashnakash@gmail.com>
2016-05-21 09:23:07 -05:00
|
|
|
#include "UserMessages.hpp"
|
|
|
|
#include "Util.hpp"
|
2015-03-04 17:14:04 -06:00
|
|
|
|
2015-04-14 09:50:38 -05:00
|
|
|
using namespace LOOLProtocol;
|
|
|
|
|
2016-10-07 11:49:37 -05:00
|
|
|
using Poco::Environment;
|
2015-05-29 00:49:49 -05:00
|
|
|
using Poco::Exception;
|
2015-04-16 11:15:40 -05:00
|
|
|
using Poco::File;
|
2016-02-24 01:39:23 -06:00
|
|
|
using Poco::Net::HTMLForm;
|
2015-03-04 17:14:04 -06:00
|
|
|
using Poco::Net::HTTPRequest;
|
|
|
|
using Poco::Net::HTTPRequestHandler;
|
|
|
|
using Poco::Net::HTTPRequestHandlerFactory;
|
|
|
|
using Poco::Net::HTTPResponse;
|
|
|
|
using Poco::Net::HTTPServer;
|
|
|
|
using Poco::Net::HTTPServerParams;
|
|
|
|
using Poco::Net::HTTPServerRequest;
|
|
|
|
using Poco::Net::HTTPServerResponse;
|
2016-02-24 01:39:23 -06:00
|
|
|
using Poco::Net::MessageHeader;
|
|
|
|
using Poco::Net::NameValueCollection;
|
|
|
|
using Poco::Net::PartHandler;
|
2016-03-21 03:37:39 -05:00
|
|
|
using Poco::Net::SecureServerSocket;
|
2015-03-04 17:14:04 -06:00
|
|
|
using Poco::Net::ServerSocket;
|
2015-05-08 13:24:46 -05:00
|
|
|
using Poco::Net::SocketAddress;
|
2015-03-04 17:14:04 -06:00
|
|
|
using Poco::Net::WebSocket;
|
2015-04-16 11:15:40 -05:00
|
|
|
using Poco::Path;
|
2016-10-12 10:15:13 -05:00
|
|
|
using Poco::Pipe;
|
2015-04-16 11:15:40 -05:00
|
|
|
using Poco::Process;
|
2016-02-24 01:39:23 -06:00
|
|
|
using Poco::ProcessHandle;
|
|
|
|
using Poco::StreamCopier;
|
2015-05-18 03:21:30 -05:00
|
|
|
using Poco::StringTokenizer;
|
2016-02-24 01:39:23 -06:00
|
|
|
using Poco::TemporaryFile;
|
2015-05-29 00:49:49 -05:00
|
|
|
using Poco::ThreadPool;
|
2016-02-24 01:39:23 -06:00
|
|
|
using Poco::URI;
|
2015-03-04 17:14:04 -06:00
|
|
|
using Poco::Util::Application;
|
|
|
|
using Poco::Util::HelpFormatter;
|
2015-04-08 09:22:42 -05:00
|
|
|
using Poco::Util::IncompatibleOptionsException;
|
|
|
|
using Poco::Util::MissingOptionException;
|
2015-03-04 17:14:04 -06:00
|
|
|
using Poco::Util::Option;
|
|
|
|
using Poco::Util::OptionSet;
|
|
|
|
using Poco::Util::ServerApplication;
|
2016-03-15 10:35:59 -05:00
|
|
|
using Poco::XML::AutoPtr;
|
|
|
|
using Poco::XML::DOMParser;
|
|
|
|
using Poco::XML::DOMWriter;
|
2016-03-22 10:25:35 -05:00
|
|
|
using Poco::XML::Element;
|
|
|
|
using Poco::XML::InputSource;
|
2016-03-15 10:35:59 -05:00
|
|
|
using Poco::XML::NodeList;
|
2016-10-13 15:51:14 -05:00
|
|
|
using Poco::XML::Node;
|
2015-03-04 17:14:04 -06:00
|
|
|
|
2016-04-14 12:04:19 -05:00
|
|
|
int ClientPortNumber = DEFAULT_CLIENT_PORT_NUMBER;
|
2016-05-04 06:06:34 -05:00
|
|
|
int MasterPortNumber = DEFAULT_MASTER_PORT_NUMBER;
|
2016-04-14 12:04:19 -05:00
|
|
|
|
2016-04-03 09:33:35 -05:00
|
|
|
/// New LOK child processes ready to host documents.
|
2016-04-04 21:40:18 -05:00
|
|
|
//TODO: Move to a more sensible namespace.
|
2016-04-15 09:07:24 -05:00
|
|
|
static bool DisplayVersion = false;
|
2016-10-16 16:56:25 -05:00
|
|
|
static std::vector<std::shared_ptr<ChildProcess>> NewChildren;
|
|
|
|
static std::mutex NewChildrenMutex;
|
|
|
|
static std::condition_variable NewChildrenCV;
|
|
|
|
static std::chrono::steady_clock::time_point LastForkRequestTime = std::chrono::steady_clock::now();
|
|
|
|
static std::map<std::string, std::shared_ptr<DocumentBroker>> DocBrokers;
|
|
|
|
static std::mutex DocBrokersMutex;
|
2016-04-09 10:54:22 -05:00
|
|
|
|
2016-04-12 05:48:42 -05:00
|
|
|
#if ENABLE_DEBUG
|
|
|
|
static int careerSpanSeconds = 0;
|
|
|
|
#endif
|
2016-01-06 23:16:47 -06:00
|
|
|
|
2016-09-29 08:48:09 -05:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
static inline
|
2016-10-04 07:10:45 -05:00
|
|
|
void shutdownLimitReached(WebSocket& ws)
|
2016-09-29 08:48:09 -05:00
|
|
|
{
|
2016-10-12 10:15:32 -05:00
|
|
|
const std::string error = Poco::format(PAYLOAD_UNAVAILABLE_LIMIT_REACHED, MAX_DOCUMENTS, MAX_CONNECTIONS);
|
2016-09-29 08:48:09 -05:00
|
|
|
|
|
|
|
/* loleaflet sends loolclient, load and partrectangles message immediately
|
|
|
|
after web socket handshake, so closing web socket fails loading page in
|
|
|
|
some sensible browsers. Ignore handshake messages and gracefully
|
|
|
|
close in order to send error messages.
|
|
|
|
*/
|
|
|
|
try
|
|
|
|
{
|
|
|
|
int flags = 0;
|
2016-10-03 13:36:02 -05:00
|
|
|
int retries = 7;
|
2016-09-29 08:48:09 -05:00
|
|
|
std::vector<char> buffer(READ_BUFFER_SIZE * 100);
|
|
|
|
|
|
|
|
// 5 seconds timeout
|
|
|
|
ws.setReceiveTimeout(5000000);
|
|
|
|
do
|
|
|
|
{
|
|
|
|
// ignore loolclient, load and partpagerectangles
|
|
|
|
ws.receiveFrame(buffer.data(), buffer.capacity(), flags);
|
2016-10-03 13:36:02 -05:00
|
|
|
if (--retries == 4)
|
2016-09-29 08:48:09 -05:00
|
|
|
{
|
2016-10-01 07:45:08 -05:00
|
|
|
ws.sendFrame(error.data(), error.size());
|
2016-10-12 04:51:06 -05:00
|
|
|
ws.shutdown(WebSocket::WS_POLICY_VIOLATION);
|
2016-09-29 08:48:09 -05:00
|
|
|
}
|
|
|
|
}
|
2016-10-03 13:36:02 -05:00
|
|
|
while (retries > 0 && (flags & WebSocket::FRAME_OP_BITMASK) != WebSocket::FRAME_OP_CLOSE);
|
2016-09-29 08:48:09 -05:00
|
|
|
}
|
2016-10-13 03:58:15 -05:00
|
|
|
catch (const Exception&)
|
2016-09-29 08:48:09 -05:00
|
|
|
{
|
2016-10-22 10:34:47 -05:00
|
|
|
// FIXME: handle exceptions thrown from here ? ...
|
2016-10-01 07:45:08 -05:00
|
|
|
ws.sendFrame(error.data(), error.size());
|
2016-10-12 04:51:06 -05:00
|
|
|
ws.shutdown(WebSocket::WS_POLICY_VIOLATION);
|
2016-09-29 08:48:09 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-04-09 15:53:33 -05:00
|
|
|
static void forkChildren(const int number)
|
2016-04-04 09:17:03 -05:00
|
|
|
{
|
2016-10-16 16:56:25 -05:00
|
|
|
Util::assertIsLocked(NewChildrenMutex);
|
2016-04-04 09:17:03 -05:00
|
|
|
|
2016-04-09 15:53:33 -05:00
|
|
|
if (number > 0)
|
|
|
|
{
|
2016-09-30 04:05:36 -05:00
|
|
|
Util::checkDiskSpaceOnRegisteredFileSystems();
|
2016-04-09 15:53:33 -05:00
|
|
|
const std::string aMessage = "spawn " + std::to_string(number) + "\n";
|
|
|
|
Log::debug("MasterToForKit: " + aMessage.substr(0, aMessage.length() - 1));
|
2016-10-12 11:07:54 -05:00
|
|
|
IoUtil::writeToPipe(LOOLWSD::ForKitWritePipe, aMessage);
|
2016-10-16 16:56:25 -05:00
|
|
|
LastForkRequestTime = std::chrono::steady_clock::now();
|
2016-04-09 15:53:33 -05:00
|
|
|
}
|
2016-04-04 09:17:03 -05:00
|
|
|
}
|
|
|
|
|
2016-05-02 21:57:27 -05:00
|
|
|
/// Called on startup only.
|
2016-04-08 03:00:58 -05:00
|
|
|
static void preForkChildren()
|
2016-04-04 09:17:03 -05:00
|
|
|
{
|
2016-10-16 16:56:25 -05:00
|
|
|
std::unique_lock<std::mutex> lock(NewChildrenMutex);
|
2016-10-05 21:06:02 -05:00
|
|
|
|
2016-04-06 13:50:55 -05:00
|
|
|
int numPreSpawn = LOOLWSD::NumPreSpawnedChildren;
|
2016-04-09 11:30:48 -05:00
|
|
|
UnitWSD::get().preSpawnCount(numPreSpawn);
|
2016-04-09 15:53:33 -05:00
|
|
|
--numPreSpawn; // ForKit always spawns one child at startup.
|
2016-10-05 21:06:02 -05:00
|
|
|
|
2016-04-06 13:50:55 -05:00
|
|
|
forkChildren(numPreSpawn);
|
2016-04-04 09:17:03 -05:00
|
|
|
}
|
|
|
|
|
2016-05-02 21:57:27 -05:00
|
|
|
static void prespawnChildren()
|
|
|
|
{
|
2016-10-16 16:56:25 -05:00
|
|
|
std::unique_lock<std::mutex> lock(NewChildrenMutex, std::defer_lock);
|
2016-05-03 07:36:53 -05:00
|
|
|
if (!lock.try_lock())
|
|
|
|
{
|
|
|
|
// We are forking already? Try later.
|
|
|
|
return;
|
|
|
|
}
|
2016-05-02 21:57:27 -05:00
|
|
|
|
2016-05-12 09:43:13 -05:00
|
|
|
// Do the cleanup first.
|
2016-10-15 16:08:55 -05:00
|
|
|
bool rebalance = false;
|
2016-10-16 16:56:25 -05:00
|
|
|
for (int i = NewChildren.size() - 1; i >= 0; --i)
|
2016-05-02 21:57:27 -05:00
|
|
|
{
|
2016-10-16 16:56:25 -05:00
|
|
|
if (!NewChildren[i]->isAlive())
|
2016-05-02 21:57:27 -05:00
|
|
|
{
|
2016-10-16 16:56:25 -05:00
|
|
|
Log::warn() << "Removing unused dead child [" << NewChildren[i]->getPid()
|
2016-10-15 16:08:55 -05:00
|
|
|
<< "]." << Log::end;
|
2016-10-16 16:56:25 -05:00
|
|
|
NewChildren.erase(NewChildren.begin() + i);
|
2016-10-15 16:08:55 -05:00
|
|
|
|
|
|
|
// Rebalance after cleanup.
|
|
|
|
rebalance = true;
|
2016-05-02 21:57:27 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-15 16:08:55 -05:00
|
|
|
int balance = LOOLWSD::NumPreSpawnedChildren;
|
2016-10-16 16:56:25 -05:00
|
|
|
balance -= NewChildren.size();
|
2016-10-15 16:08:55 -05:00
|
|
|
if (balance <= 0)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-10-16 16:56:25 -05:00
|
|
|
const auto duration = (std::chrono::steady_clock::now() - LastForkRequestTime);
|
2016-10-15 16:08:55 -05:00
|
|
|
if (!rebalance &&
|
|
|
|
std::chrono::duration_cast<std::chrono::milliseconds>(duration).count() <= CHILD_TIMEOUT_MS)
|
2016-05-12 09:43:13 -05:00
|
|
|
{
|
|
|
|
// Not enough time passed to balance children.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-05-02 21:57:27 -05:00
|
|
|
forkChildren(balance);
|
|
|
|
}
|
|
|
|
|
2016-05-07 08:42:10 -05:00
|
|
|
static size_t addNewChild(const std::shared_ptr<ChildProcess>& child)
|
|
|
|
{
|
2016-10-16 16:56:25 -05:00
|
|
|
std::unique_lock<std::mutex> lock(NewChildrenMutex);
|
|
|
|
NewChildren.emplace_back(child);
|
|
|
|
const auto count = NewChildren.size();
|
2016-05-07 08:42:10 -05:00
|
|
|
Log::info() << "Have " << count << " "
|
|
|
|
<< (count == 1 ? "child" : "children")
|
|
|
|
<< "." << Log::end;
|
|
|
|
|
2016-10-16 16:56:25 -05:00
|
|
|
NewChildrenCV.notify_one();
|
2016-05-07 08:42:10 -05:00
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
2016-04-08 03:00:58 -05:00
|
|
|
static std::shared_ptr<ChildProcess> getNewChild()
|
2016-04-03 18:00:37 -05:00
|
|
|
{
|
2016-10-16 16:56:25 -05:00
|
|
|
std::unique_lock<std::mutex> lock(NewChildrenMutex);
|
2016-04-03 18:00:37 -05:00
|
|
|
|
2016-04-24 10:56:02 -05:00
|
|
|
namespace chrono = std::chrono;
|
|
|
|
const auto startTime = chrono::steady_clock::now();
|
|
|
|
do
|
2016-04-03 18:00:37 -05:00
|
|
|
{
|
2016-10-16 16:56:25 -05:00
|
|
|
const int available = NewChildren.size();
|
2016-04-24 10:56:02 -05:00
|
|
|
int balance = LOOLWSD::NumPreSpawnedChildren;
|
|
|
|
if (available == 0)
|
|
|
|
{
|
|
|
|
Log::error("getNewChild: No available child. Sending spawn request to forkit and failing.");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
balance -= available - 1; // Minus the one we'll dispatch just now.
|
2016-05-03 06:27:46 -05:00
|
|
|
balance = std::max(balance, 0);
|
2016-04-24 10:56:02 -05:00
|
|
|
}
|
2016-04-03 18:00:37 -05:00
|
|
|
|
2016-04-24 10:56:02 -05:00
|
|
|
Log::debug("getNewChild: Have " + std::to_string(available) + " children, forking " + std::to_string(balance));
|
|
|
|
forkChildren(balance);
|
2016-04-03 18:00:37 -05:00
|
|
|
|
2016-10-14 22:00:33 -05:00
|
|
|
const auto timeout = chrono::milliseconds(CHILD_TIMEOUT_MS);
|
2016-10-16 16:56:25 -05:00
|
|
|
if (NewChildrenCV.wait_for(lock, timeout, [](){ return !NewChildren.empty(); }))
|
2016-04-24 10:56:02 -05:00
|
|
|
{
|
2016-10-16 16:56:25 -05:00
|
|
|
auto child = NewChildren.back();
|
|
|
|
NewChildren.pop_back();
|
2016-04-24 10:56:02 -05:00
|
|
|
|
|
|
|
// Validate before returning.
|
|
|
|
if (child && child->isAlive())
|
|
|
|
{
|
|
|
|
Log::debug("getNewChild: Returning new child [" + std::to_string(child->getPid()) + "].");
|
|
|
|
return child;
|
|
|
|
}
|
|
|
|
}
|
2016-05-12 09:43:13 -05:00
|
|
|
|
|
|
|
Log::debug("getNewChild: No live child, forking more.");
|
2016-04-03 18:00:37 -05:00
|
|
|
}
|
2016-10-14 22:00:33 -05:00
|
|
|
while (chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now() - startTime).count() <
|
|
|
|
CHILD_TIMEOUT_MS * 4);
|
2016-04-03 18:00:37 -05:00
|
|
|
|
2016-04-24 10:56:02 -05:00
|
|
|
Log::debug("getNewChild: Timed out while waiting for new child.");
|
2016-04-03 18:00:37 -05:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2015-10-16 10:38:24 -05:00
|
|
|
/// Handles the filename part of the convert-to POST request payload.
|
2016-02-24 01:39:23 -06:00
|
|
|
class ConvertToPartHandler : public PartHandler
|
2015-10-16 10:38:24 -05:00
|
|
|
{
|
2015-10-19 09:03:16 -05:00
|
|
|
std::string& _filename;
|
2015-10-16 10:38:24 -05:00
|
|
|
public:
|
2015-10-20 08:00:05 -05:00
|
|
|
ConvertToPartHandler(std::string& filename)
|
|
|
|
: _filename(filename)
|
2015-10-16 10:38:24 -05:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2016-02-24 01:39:23 -06:00
|
|
|
virtual void handlePart(const MessageHeader& header, std::istream& stream) override
|
2015-10-16 10:38:24 -05:00
|
|
|
{
|
2015-10-20 08:00:05 -05:00
|
|
|
// Extract filename and put it to a temporary directory.
|
2015-10-19 09:03:16 -05:00
|
|
|
std::string disp;
|
2016-02-24 01:39:23 -06:00
|
|
|
NameValueCollection params;
|
2015-10-19 09:03:16 -05:00
|
|
|
if (header.has("Content-Disposition"))
|
|
|
|
{
|
|
|
|
std::string cd = header.get("Content-Disposition");
|
2016-02-24 01:39:23 -06:00
|
|
|
MessageHeader::splitParameters(cd, disp, params);
|
2015-10-19 09:03:16 -05:00
|
|
|
}
|
2015-10-22 10:27:29 -05:00
|
|
|
|
2015-10-20 08:00:05 -05:00
|
|
|
if (!params.has("filename"))
|
|
|
|
return;
|
2015-10-19 09:03:16 -05:00
|
|
|
|
2016-04-18 07:33:22 -05:00
|
|
|
Path tempPath = Path::forDirectory(TemporaryFile().tempName() + "/");
|
2015-10-20 08:00:05 -05:00
|
|
|
File(tempPath).createDirectories();
|
2016-07-21 04:26:26 -05:00
|
|
|
// Prevent user inputting anything funny here.
|
|
|
|
// A "filename" should always be a filename, not a path
|
|
|
|
const Path filenameParam(params.get("filename"));
|
|
|
|
tempPath.setFileName(filenameParam.getFileName());
|
2015-10-20 08:00:05 -05:00
|
|
|
_filename = tempPath.toString();
|
|
|
|
|
|
|
|
// Copy the stream to _filename.
|
|
|
|
std::ofstream fileStream;
|
|
|
|
fileStream.open(_filename);
|
2016-02-24 01:39:23 -06:00
|
|
|
StreamCopier::copyStream(stream, fileStream);
|
2015-10-20 08:00:05 -05:00
|
|
|
fileStream.close();
|
2015-10-16 10:38:24 -05:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-01-05 20:29:12 -06:00
|
|
|
/// Handle a public connection from a client.
|
|
|
|
class ClientRequestHandler: public HTTPRequestHandler
|
|
|
|
{
|
2016-01-24 13:48:09 -06:00
|
|
|
private:
|
2016-10-13 15:51:14 -05:00
|
|
|
static std::string getContentType(const std::string& fileName)
|
|
|
|
{
|
|
|
|
const std::string nodePath = Poco::format("//[@ext='%s']", Poco::Path(fileName).getExtension());
|
|
|
|
std::string discPath = Path(Application::instance().commandPath()).parent().toString() + "discovery.xml";
|
|
|
|
if (!File(discPath).exists())
|
|
|
|
{
|
|
|
|
discPath = LOOLWSD_DATADIR "/discovery.xml";
|
|
|
|
}
|
|
|
|
|
|
|
|
InputSource input(discPath);
|
|
|
|
DOMParser domParser;
|
|
|
|
AutoPtr<Poco::XML::Document> doc = domParser.parse(&input);
|
|
|
|
// TODO. discovery.xml missing application/pdf
|
|
|
|
Node* node = doc->getNodeByPath(nodePath);
|
|
|
|
if (node && (node = node->parentNode()) && node->hasAttributes())
|
|
|
|
{
|
|
|
|
return dynamic_cast<Element*>(node)->getAttribute("name");
|
|
|
|
}
|
|
|
|
|
|
|
|
return "application/octet-stream";
|
|
|
|
}
|
|
|
|
|
2016-04-16 11:56:23 -05:00
|
|
|
/// Handle POST requests.
|
|
|
|
/// Always throw on error, do not set response status here.
|
|
|
|
/// Returns true if a response has been sent.
|
|
|
|
static bool handlePostRequest(HTTPServerRequest& request, HTTPServerResponse& response, const std::string& id)
|
2015-03-04 17:14:04 -06:00
|
|
|
{
|
2016-03-15 10:35:59 -05:00
|
|
|
Log::info("Post request: [" + request.getURI() + "]");
|
2016-01-24 13:48:09 -06:00
|
|
|
StringTokenizer tokens(request.getURI(), "/?");
|
2016-06-09 03:36:45 -05:00
|
|
|
if (tokens.count() >= 3 && tokens[2] == "convert-to")
|
2015-03-04 17:14:04 -06:00
|
|
|
{
|
2016-01-24 13:48:09 -06:00
|
|
|
std::string fromPath;
|
|
|
|
ConvertToPartHandler handler(fromPath);
|
2016-02-24 01:39:23 -06:00
|
|
|
HTMLForm form(request, request.stream(), handler);
|
2016-03-13 13:00:19 -05:00
|
|
|
const std::string format = (form.has("format") ? form.get("format") : "");
|
2016-01-24 13:48:09 -06:00
|
|
|
|
|
|
|
bool sent = false;
|
|
|
|
if (!fromPath.empty())
|
2015-10-16 10:38:24 -05:00
|
|
|
{
|
2016-01-24 13:48:09 -06:00
|
|
|
if (!format.empty())
|
2015-10-16 10:38:24 -05:00
|
|
|
{
|
2016-03-13 13:00:19 -05:00
|
|
|
Log::info("Conversion request for URI [" + fromPath + "].");
|
2016-04-03 20:40:14 -05:00
|
|
|
|
|
|
|
// Request a kit process for this doc.
|
|
|
|
auto child = getNewChild();
|
|
|
|
if (!child)
|
|
|
|
{
|
|
|
|
// Let the client know we can't serve now.
|
2016-04-16 11:56:23 -05:00
|
|
|
throw std::runtime_error("Failed to spawn lokit child.");
|
2016-04-03 20:40:14 -05:00
|
|
|
}
|
|
|
|
|
2016-03-19 17:49:36 -05:00
|
|
|
auto uriPublic = DocumentBroker::sanitizeURI(fromPath);
|
|
|
|
const auto docKey = DocumentBroker::getDocKey(uriPublic);
|
2016-10-09 12:42:30 -05:00
|
|
|
Log::debug("New DocumentBroker for docKey [" + docKey + "].");
|
2016-04-03 20:40:14 -05:00
|
|
|
auto docBroker = std::make_shared<DocumentBroker>(uriPublic, docKey, LOOLWSD::ChildRoot, child);
|
2016-10-09 12:42:30 -05:00
|
|
|
child->setDocumentBroker(docBroker);
|
2016-03-13 13:00:19 -05:00
|
|
|
|
|
|
|
// This lock could become a bottleneck.
|
|
|
|
// In that case, we can use a pool and index by publicPath.
|
2016-10-16 16:56:25 -05:00
|
|
|
std::unique_lock<std::mutex> lock(DocBrokersMutex);
|
2016-03-13 13:00:19 -05:00
|
|
|
|
2016-04-16 11:56:23 -05:00
|
|
|
//FIXME: What if the same document is already open? Need a fake dockey here?
|
2016-03-13 13:00:19 -05:00
|
|
|
Log::debug("New DocumentBroker for docKey [" + docKey + "].");
|
2016-10-16 16:56:25 -05:00
|
|
|
DocBrokers.emplace(docKey, docBroker);
|
2016-03-13 13:00:19 -05:00
|
|
|
|
2016-01-24 13:48:09 -06:00
|
|
|
// Load the document.
|
|
|
|
std::shared_ptr<WebSocket> ws;
|
2016-10-16 11:40:52 -05:00
|
|
|
auto session = std::make_shared<ClientSession>(id, ws, docBroker, uriPublic);
|
2016-04-24 21:09:13 -05:00
|
|
|
|
2016-04-16 16:18:51 -05:00
|
|
|
auto sessionsCount = docBroker->addSession(session);
|
2016-04-24 21:09:13 -05:00
|
|
|
Log::trace(docKey + ", ws_sessions++: " + std::to_string(sessionsCount));
|
|
|
|
|
2016-03-13 13:00:19 -05:00
|
|
|
lock.unlock();
|
|
|
|
|
2016-01-24 13:48:09 -06:00
|
|
|
std::string encodedFrom;
|
2016-03-13 13:00:19 -05:00
|
|
|
URI::encode(docBroker->getPublicUri().getPath(), "", encodedFrom);
|
2016-01-24 13:48:09 -06:00
|
|
|
const std::string load = "load url=" + encodedFrom;
|
|
|
|
session->handleInput(load.data(), load.size());
|
|
|
|
|
2016-05-19 19:28:02 -05:00
|
|
|
//FIXME: Check for security violations.
|
2016-03-13 13:00:19 -05:00
|
|
|
Path toPath(docBroker->getPublicUri().getPath());
|
2016-01-24 13:48:09 -06:00
|
|
|
toPath.setExtension(format);
|
2016-03-28 06:01:19 -05:00
|
|
|
const std::string toJailURL = "file://" + std::string(JAILED_DOCUMENT_ROOT) + toPath.getFileName();
|
2016-01-24 13:48:09 -06:00
|
|
|
std::string encodedTo;
|
2016-02-22 08:30:42 -06:00
|
|
|
URI::encode(toJailURL, "", encodedTo);
|
2016-05-19 19:28:02 -05:00
|
|
|
|
|
|
|
// Convert it to the requested format.
|
|
|
|
const auto saveas = "saveas url=" + encodedTo + " format=" + format + " options=";
|
2016-01-24 13:48:09 -06:00
|
|
|
session->handleInput(saveas.data(), saveas.size());
|
2016-01-23 18:41:01 -06:00
|
|
|
|
2016-01-24 13:48:09 -06:00
|
|
|
// Send it back to the client.
|
2016-10-09 12:42:30 -05:00
|
|
|
try
|
|
|
|
{
|
|
|
|
Poco::URI resultURL(session->getSaveAsUrl(COMMAND_TIMEOUT_MS));
|
|
|
|
Log::trace("Save-as URL: " + resultURL.toString());
|
|
|
|
|
|
|
|
if (!resultURL.getPath().empty())
|
|
|
|
{
|
|
|
|
const std::string mimeType = "application/octet-stream";
|
|
|
|
std::string encodedFilePath;
|
|
|
|
URI::encode(resultURL.getPath(), "", encodedFilePath);
|
|
|
|
Log::trace("Sending file: " + encodedFilePath);
|
|
|
|
response.sendFile(encodedFilePath, mimeType);
|
|
|
|
sent = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (const std::exception& ex)
|
2016-01-24 13:48:09 -06:00
|
|
|
{
|
2016-10-18 00:32:18 -05:00
|
|
|
Log::error("Failed to get save-as url: " + std::string(ex.what()));
|
2016-01-23 18:41:01 -06:00
|
|
|
}
|
2016-03-13 13:00:19 -05:00
|
|
|
|
|
|
|
lock.lock();
|
2016-04-16 16:18:51 -05:00
|
|
|
sessionsCount = docBroker->removeSession(id);
|
|
|
|
if (sessionsCount == 0)
|
2016-03-13 13:00:19 -05:00
|
|
|
{
|
2016-10-14 21:52:33 -05:00
|
|
|
// At this point we're done.
|
|
|
|
// We can't save if we hadn't, just kill.
|
|
|
|
Log::debug("Closing child for docKey [" + docKey + "].");
|
|
|
|
child->close(true);
|
2016-03-13 13:00:19 -05:00
|
|
|
Log::debug("Removing DocumentBroker for docKey [" + docKey + "].");
|
2016-10-16 16:56:25 -05:00
|
|
|
DocBrokers.erase(docKey);
|
2016-03-13 13:00:19 -05:00
|
|
|
}
|
2016-05-19 19:28:02 -05:00
|
|
|
else
|
|
|
|
{
|
|
|
|
Log::error("Multiple sessions during conversion. " + std::to_string(sessionsCount) + " sessions remain.");
|
|
|
|
}
|
2016-10-09 12:42:30 -05:00
|
|
|
|
2016-10-12 05:05:57 -05:00
|
|
|
session->shutdownPeer(WebSocket::WS_NORMAL_CLOSE);
|
2015-10-20 08:35:43 -05:00
|
|
|
}
|
2016-01-23 18:41:01 -06:00
|
|
|
|
2016-01-24 13:48:09 -06:00
|
|
|
// Clean up the temporary directory the HTMLForm ctor created.
|
|
|
|
Path tempDirectory(fromPath);
|
|
|
|
tempDirectory.setFileName("");
|
|
|
|
Util::removeFile(tempDirectory, /*recursive=*/true);
|
2015-10-16 10:38:24 -05:00
|
|
|
}
|
2016-01-24 13:48:09 -06:00
|
|
|
|
|
|
|
if (!sent)
|
2015-10-22 10:27:29 -05:00
|
|
|
{
|
2016-04-16 11:56:23 -05:00
|
|
|
//TODO: We should differentiate between bad request and failed conversion.
|
|
|
|
throw BadRequestException("Failed to convert and send file.");
|
2016-01-24 13:48:09 -06:00
|
|
|
}
|
2016-04-16 11:56:23 -05:00
|
|
|
|
|
|
|
return true;
|
2016-01-24 13:48:09 -06:00
|
|
|
}
|
2016-09-29 10:42:50 -05:00
|
|
|
else if (tokens.count() >= 4 && tokens[3] == "insertfile")
|
2016-01-24 13:48:09 -06:00
|
|
|
{
|
|
|
|
Log::info("Insert file request.");
|
|
|
|
response.set("Access-Control-Allow-Origin", "*");
|
|
|
|
response.set("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
|
|
response.set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
2015-10-22 10:27:29 -05:00
|
|
|
|
2016-01-24 13:48:09 -06:00
|
|
|
std::string tmpPath;
|
|
|
|
ConvertToPartHandler handler(tmpPath);
|
2016-02-24 01:39:23 -06:00
|
|
|
HTMLForm form(request, request.stream(), handler);
|
2015-10-22 10:27:29 -05:00
|
|
|
|
2016-04-16 11:56:23 -05:00
|
|
|
if (form.has("childid") && form.has("name"))
|
2016-01-24 13:48:09 -06:00
|
|
|
{
|
2016-04-16 11:56:23 -05:00
|
|
|
const std::string formChildid(form.get("childid"));
|
|
|
|
const std::string formName(form.get("name"));
|
|
|
|
|
2016-09-29 10:42:50 -05:00
|
|
|
// Validate the docKey
|
2016-10-16 16:56:25 -05:00
|
|
|
std::unique_lock<std::mutex> DocBrokersLock(DocBrokersMutex);
|
2016-09-29 10:42:50 -05:00
|
|
|
std::string decodedUri;
|
|
|
|
URI::decode(tokens[2], decodedUri);
|
|
|
|
const auto docKey = DocumentBroker::getDocKey(DocumentBroker::sanitizeURI(decodedUri));
|
2016-10-16 16:56:25 -05:00
|
|
|
auto docBrokerIt = DocBrokers.find(docKey);
|
2016-09-29 10:42:50 -05:00
|
|
|
|
|
|
|
// Maybe just free the client from sending childid in form ?
|
2016-10-16 16:56:25 -05:00
|
|
|
if (docBrokerIt == DocBrokers.end() || docBrokerIt->second->getJailId() != formChildid)
|
2016-09-29 10:42:50 -05:00
|
|
|
{
|
|
|
|
throw BadRequestException("DocKey [" + docKey + "] or childid [" + formChildid + "] is invalid.");
|
|
|
|
}
|
2016-10-16 16:56:25 -05:00
|
|
|
DocBrokersLock.unlock();
|
2016-09-29 10:42:50 -05:00
|
|
|
|
2016-04-16 11:56:23 -05:00
|
|
|
// protect against attempts to inject something funny here
|
|
|
|
if (formChildid.find('/') == std::string::npos && formName.find('/') == std::string::npos)
|
2016-01-13 09:35:55 -06:00
|
|
|
{
|
2016-01-24 13:48:09 -06:00
|
|
|
Log::info() << "Perform insertfile: " << formChildid << ", " << formName << Log::end;
|
|
|
|
const std::string dirPath = LOOLWSD::ChildRoot + formChildid
|
2016-03-28 06:01:19 -05:00
|
|
|
+ JAILED_DOCUMENT_ROOT + "insertfile";
|
2016-01-24 13:48:09 -06:00
|
|
|
File(dirPath).createDirectories();
|
2016-04-18 07:33:22 -05:00
|
|
|
std::string fileName = dirPath + "/" + form.get("name");
|
2016-01-24 13:48:09 -06:00
|
|
|
File(tmpPath).moveTo(fileName);
|
2016-04-16 11:56:23 -05:00
|
|
|
return false;
|
2015-10-22 10:27:29 -05:00
|
|
|
}
|
|
|
|
}
|
2016-01-24 13:48:09 -06:00
|
|
|
}
|
2016-09-29 11:22:45 -05:00
|
|
|
else if (tokens.count() >= 6)
|
2016-01-24 13:48:09 -06:00
|
|
|
{
|
|
|
|
Log::info("File download request.");
|
2016-04-16 11:56:23 -05:00
|
|
|
//TODO: Check that the user in question has access to this file!
|
2016-09-29 11:22:45 -05:00
|
|
|
|
2016-10-03 00:21:20 -05:00
|
|
|
// 1. Validate the dockey
|
2016-09-29 11:22:45 -05:00
|
|
|
std::string decodedUri;
|
|
|
|
URI::decode(tokens[2], decodedUri);
|
|
|
|
const auto docKey = DocumentBroker::getDocKey(DocumentBroker::sanitizeURI(decodedUri));
|
2016-10-16 16:56:25 -05:00
|
|
|
std::unique_lock<std::mutex> DocBrokersLock(DocBrokersMutex);
|
|
|
|
auto docBrokerIt = DocBrokers.find(docKey);
|
|
|
|
if (docBrokerIt == DocBrokers.end())
|
2016-09-29 11:22:45 -05:00
|
|
|
{
|
|
|
|
throw BadRequestException("DocKey [" + docKey + "] is invalid.");
|
|
|
|
}
|
2016-10-03 00:21:20 -05:00
|
|
|
|
|
|
|
// 2. Cross-check if received child id is correct
|
|
|
|
if (docBrokerIt->second->getJailId() != tokens[3])
|
|
|
|
{
|
|
|
|
throw BadRequestException("ChildId does not correspond to docKey");
|
|
|
|
}
|
|
|
|
|
|
|
|
// 3. Don't let user download the file in main doc directory containing
|
|
|
|
// the document being edited otherwise we will end up deleting main directory
|
|
|
|
// after download finishes
|
|
|
|
if (docBrokerIt->second->getJailId() == tokens[4])
|
|
|
|
{
|
|
|
|
throw BadRequestException("RandomDir cannot be equal to ChildId");
|
|
|
|
}
|
2016-10-16 16:56:25 -05:00
|
|
|
DocBrokersLock.unlock();
|
2016-09-29 11:22:45 -05:00
|
|
|
|
2016-10-03 00:21:20 -05:00
|
|
|
std::string fileName;
|
|
|
|
bool responded = false;
|
|
|
|
URI::decode(tokens[5], fileName);
|
|
|
|
const Path filePath(LOOLWSD::ChildRoot + tokens[3]
|
|
|
|
+ JAILED_DOCUMENT_ROOT + tokens[4] + "/" + fileName);
|
|
|
|
Log::info("HTTP request for: " + filePath.toString());
|
|
|
|
if (filePath.isAbsolute() && File(filePath).exists())
|
2016-01-24 13:48:09 -06:00
|
|
|
{
|
|
|
|
response.set("Access-Control-Allow-Origin", "*");
|
2016-10-03 00:21:20 -05:00
|
|
|
try
|
|
|
|
{
|
2016-10-13 15:51:14 -05:00
|
|
|
response.sendFile(filePath.toString(), getContentType(fileName));
|
2016-10-03 00:21:20 -05:00
|
|
|
responded = true;
|
|
|
|
}
|
|
|
|
catch (const Exception& exc)
|
|
|
|
{
|
2016-10-07 05:51:55 -05:00
|
|
|
Log::error() << "Error sending file to client: " << exc.displayText()
|
2016-10-03 00:21:20 -05:00
|
|
|
<< (exc.nested() ? " (" + exc.nested()->displayText() + ")" : "")
|
|
|
|
<< Log::end;
|
|
|
|
}
|
|
|
|
|
|
|
|
Util::removeFile(File(filePath.parent()).path(), true);
|
2015-10-09 07:55:49 -05:00
|
|
|
}
|
2016-05-30 20:43:18 -05:00
|
|
|
else
|
|
|
|
{
|
2016-10-03 00:21:20 -05:00
|
|
|
Log::error("Download file [" + filePath.toString() + "] not found.");
|
2016-05-30 20:43:18 -05:00
|
|
|
}
|
2016-10-03 00:21:20 -05:00
|
|
|
|
|
|
|
return responded;
|
2015-03-04 17:14:04 -06:00
|
|
|
}
|
2016-04-16 11:56:23 -05:00
|
|
|
|
|
|
|
throw BadRequestException("Invalid or unknown request.");
|
2016-01-24 13:48:09 -06:00
|
|
|
}
|
2015-04-21 07:06:41 -05:00
|
|
|
|
2016-04-16 11:56:23 -05:00
|
|
|
/// Handle GET requests.
|
2016-09-29 12:20:52 -05:00
|
|
|
static void handleGetRequest(const std::string& uri, std::shared_ptr<WebSocket>& ws, const std::string& id)
|
2016-01-24 13:48:09 -06:00
|
|
|
{
|
2016-03-10 21:33:03 -06:00
|
|
|
Log::info("Starting GET request handler for session [" + id + "].");
|
2016-02-15 17:05:24 -06:00
|
|
|
|
2016-04-24 10:08:08 -05:00
|
|
|
// indicator to the client that document broker is searching
|
|
|
|
std::string status("statusindicator: find");
|
2016-04-26 22:44:23 -05:00
|
|
|
Log::trace("Sending to Client [" + status + "].");
|
2016-04-24 10:08:08 -05:00
|
|
|
ws->sendFrame(status.data(), (int) status.size());
|
|
|
|
|
2016-03-19 17:49:36 -05:00
|
|
|
const auto uriPublic = DocumentBroker::sanitizeURI(uri);
|
|
|
|
const auto docKey = DocumentBroker::getDocKey(uriPublic);
|
|
|
|
std::shared_ptr<DocumentBroker> docBroker;
|
2016-03-12 18:29:17 -06:00
|
|
|
|
2016-10-16 16:56:25 -05:00
|
|
|
// scope the DocBrokersLock
|
2016-03-13 12:21:36 -05:00
|
|
|
{
|
2016-10-16 16:56:25 -05:00
|
|
|
std::unique_lock<std::mutex> DocBrokersLock(DocBrokersMutex);
|
2016-05-09 04:41:37 -05:00
|
|
|
|
|
|
|
// Lookup this document.
|
2016-10-16 16:56:25 -05:00
|
|
|
auto it = DocBrokers.find(docKey);
|
|
|
|
if (it != DocBrokers.end())
|
2016-05-09 04:41:37 -05:00
|
|
|
{
|
|
|
|
// Get the DocumentBroker from the Cache.
|
|
|
|
Log::debug("Found DocumentBroker for docKey [" + docKey + "].");
|
|
|
|
docBroker = it->second;
|
|
|
|
assert(docBroker);
|
|
|
|
}
|
2016-09-15 05:41:57 -05:00
|
|
|
else
|
|
|
|
{
|
|
|
|
// Store a dummy (marked to destroy) document broker until we
|
|
|
|
// have the real one, so that the other requests block
|
|
|
|
Log::debug("Inserting a dummy DocumentBroker for docKey [" + docKey + "] temporarily.");
|
|
|
|
|
|
|
|
std::shared_ptr<DocumentBroker> tempBroker = std::make_shared<DocumentBroker>();
|
2016-10-16 16:56:25 -05:00
|
|
|
DocBrokers.emplace(docKey, tempBroker);
|
2016-09-15 05:41:57 -05:00
|
|
|
}
|
2016-04-24 11:36:27 -05:00
|
|
|
}
|
2016-04-17 22:29:03 -05:00
|
|
|
|
2016-09-15 10:22:28 -05:00
|
|
|
if (docBroker && docBroker->isMarkedToDestroy())
|
2016-04-24 11:36:27 -05:00
|
|
|
{
|
2016-04-17 22:29:03 -05:00
|
|
|
// If this document is going out, wait.
|
2016-09-15 10:22:28 -05:00
|
|
|
Log::debug("Document [" + docKey + "] is marked to destroy, waiting to reload.");
|
|
|
|
|
2016-10-14 22:00:33 -05:00
|
|
|
const auto timeout = POLL_TIMEOUT_MS;
|
2016-09-15 10:22:28 -05:00
|
|
|
bool timedOut = true;
|
|
|
|
for (size_t i = 0; i < COMMAND_TIMEOUT_MS / timeout; ++i)
|
2016-04-17 22:29:03 -05:00
|
|
|
{
|
2016-09-15 10:22:28 -05:00
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(timeout));
|
2016-09-15 09:14:59 -05:00
|
|
|
|
2016-10-16 16:56:25 -05:00
|
|
|
std::unique_lock<std::mutex> lock(DocBrokersMutex);
|
|
|
|
auto it = DocBrokers.find(docKey);
|
|
|
|
if (it == DocBrokers.end())
|
2016-09-15 10:22:28 -05:00
|
|
|
{
|
|
|
|
// went away successfully
|
|
|
|
docBroker.reset();
|
2016-09-15 10:48:01 -05:00
|
|
|
Log::debug("Inserting a dummy DocumentBroker for docKey [" + docKey + "] temporarily after the other instance is gone.");
|
|
|
|
|
|
|
|
std::shared_ptr<DocumentBroker> tempBroker = std::make_shared<DocumentBroker>();
|
2016-10-16 16:56:25 -05:00
|
|
|
DocBrokers.emplace(docKey, tempBroker);
|
2016-09-15 10:48:01 -05:00
|
|
|
|
2016-09-15 10:22:28 -05:00
|
|
|
timedOut = false;
|
|
|
|
break;
|
2016-04-17 22:29:03 -05:00
|
|
|
}
|
2016-09-15 10:22:28 -05:00
|
|
|
else if (it->second && !it->second->isMarkedToDestroy())
|
2016-04-17 22:29:03 -05:00
|
|
|
{
|
2016-09-15 10:22:28 -05:00
|
|
|
// was actually replaced by a real document
|
|
|
|
docBroker = it->second;
|
|
|
|
timedOut = false;
|
|
|
|
break;
|
2016-04-17 22:29:03 -05:00
|
|
|
}
|
|
|
|
}
|
2016-09-15 10:22:28 -05:00
|
|
|
|
|
|
|
if (timedOut)
|
|
|
|
{
|
|
|
|
// Still here, but marked to destroy. Proceed and hope to recover.
|
|
|
|
Log::error("Timed out while waiting for document to unload before loading.");
|
|
|
|
}
|
2016-03-13 12:21:36 -05:00
|
|
|
}
|
2016-04-17 22:29:03 -05:00
|
|
|
|
2016-04-24 11:36:27 -05:00
|
|
|
bool newDoc = false;
|
2016-04-17 22:29:03 -05:00
|
|
|
if (!docBroker)
|
2016-03-13 12:21:36 -05:00
|
|
|
{
|
2016-04-24 11:36:27 -05:00
|
|
|
newDoc = true;
|
2016-04-03 20:40:14 -05:00
|
|
|
// Request a kit process for this doc.
|
|
|
|
auto child = getNewChild();
|
|
|
|
if (!child)
|
|
|
|
{
|
|
|
|
// Let the client know we can't serve now.
|
2016-04-24 11:36:27 -05:00
|
|
|
Log::error("Failed to get new child. Service Unavailable.");
|
2016-10-12 10:15:32 -05:00
|
|
|
throw WebSocketErrorMessageException(SERVICE_UNAVAILABLE_INTERNAL_ERROR);
|
2016-04-03 20:40:14 -05:00
|
|
|
}
|
|
|
|
|
2016-07-13 21:01:23 -05:00
|
|
|
#if MAX_DOCUMENTS > 0
|
2016-10-18 00:06:16 -05:00
|
|
|
std::unique_lock<std::mutex> DocBrokersLock(DocBrokersMutex);
|
2016-10-22 10:09:18 -05:00
|
|
|
if (DocBrokers.size() + 1 > MAX_DOCUMENTS)
|
2016-07-13 21:01:23 -05:00
|
|
|
{
|
|
|
|
Log::error("Maximum number of open documents reached.");
|
2016-10-04 07:10:45 -05:00
|
|
|
shutdownLimitReached(*ws);
|
2016-09-29 08:48:09 -05:00
|
|
|
return;
|
2016-07-13 21:01:23 -05:00
|
|
|
}
|
2016-10-18 00:06:16 -05:00
|
|
|
DocBrokersLock.unlock();
|
2016-07-13 21:01:23 -05:00
|
|
|
#endif
|
|
|
|
|
2016-03-13 12:21:36 -05:00
|
|
|
// Set one we just created.
|
|
|
|
Log::debug("New DocumentBroker for docKey [" + docKey + "].");
|
2016-04-03 20:40:14 -05:00
|
|
|
docBroker = std::make_shared<DocumentBroker>(uriPublic, docKey, LOOLWSD::ChildRoot, child);
|
2016-05-02 06:21:30 -05:00
|
|
|
child->setDocumentBroker(docBroker);
|
2016-04-24 11:36:27 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Validate the broker.
|
|
|
|
if (!docBroker || !docBroker->isAlive())
|
|
|
|
{
|
2016-07-08 08:04:46 -05:00
|
|
|
Log::error("DocBroker is invalid or premature termination of child process. Service Unavailable.");
|
2016-04-24 11:36:27 -05:00
|
|
|
if (!newDoc)
|
|
|
|
{
|
|
|
|
// Remove.
|
2016-10-16 16:56:25 -05:00
|
|
|
std::unique_lock<std::mutex> lock(DocBrokersMutex);
|
|
|
|
DocBrokers.erase(docKey);
|
2016-04-24 11:36:27 -05:00
|
|
|
}
|
2016-05-12 09:37:54 -05:00
|
|
|
|
2016-10-12 10:15:32 -05:00
|
|
|
throw WebSocketErrorMessageException(SERVICE_UNAVAILABLE_INTERNAL_ERROR);
|
2016-03-12 18:29:17 -06:00
|
|
|
}
|
2016-03-10 21:33:03 -06:00
|
|
|
|
2016-04-24 11:36:27 -05:00
|
|
|
if (newDoc)
|
2016-04-09 10:54:22 -05:00
|
|
|
{
|
2016-10-16 16:56:25 -05:00
|
|
|
std::unique_lock<std::mutex> lock(DocBrokersMutex);
|
|
|
|
DocBrokers[docKey] = docBroker;
|
2016-04-09 10:54:22 -05:00
|
|
|
}
|
2016-04-16 11:56:23 -05:00
|
|
|
|
2016-07-10 23:50:25 -05:00
|
|
|
// Check if readonly session is required
|
|
|
|
bool isReadOnly = false;
|
2016-10-15 11:29:35 -05:00
|
|
|
for (const auto& param: uriPublic.getQueryParameters())
|
2016-07-10 23:50:25 -05:00
|
|
|
{
|
|
|
|
Log::debug("Query param: " + param.first + ", value: " + param.second);
|
|
|
|
if (param.first == "permission")
|
|
|
|
isReadOnly = param.second == "readonly";
|
|
|
|
}
|
|
|
|
|
2016-10-19 09:52:53 -05:00
|
|
|
// In case of WOPI and if this session is not set as readonly, it might be set so
|
|
|
|
// later after making a call to WOPI host which tells us the permission on files
|
|
|
|
// (UserCanWrite param)
|
2016-10-16 11:46:46 -05:00
|
|
|
auto session = std::make_shared<ClientSession>(id, ws, docBroker, uriPublic, isReadOnly);
|
|
|
|
|
|
|
|
// Above this point exceptions are safe and will auto-cleanup.
|
|
|
|
// Below this, we need to cleanup internal references.
|
|
|
|
try
|
|
|
|
{
|
2016-04-24 11:36:27 -05:00
|
|
|
// indicator to a client that is waiting to connect to lokit process
|
|
|
|
status = "statusindicator: connect";
|
2016-04-26 22:44:23 -05:00
|
|
|
Log::trace("Sending to Client [" + status + "].");
|
2016-04-24 11:36:27 -05:00
|
|
|
ws->sendFrame(status.data(), (int) status.size());
|
|
|
|
|
2016-07-27 09:57:08 -05:00
|
|
|
// Now the bridge beetween the client and kit process is connected
|
2016-04-24 11:36:27 -05:00
|
|
|
status = "statusindicator: ready";
|
2016-04-26 22:44:23 -05:00
|
|
|
Log::trace("Sending to Client [" + status + "].");
|
2016-04-24 11:36:27 -05:00
|
|
|
ws->sendFrame(status.data(), (int) status.size());
|
2016-01-05 20:29:12 -06:00
|
|
|
|
2016-09-30 04:05:36 -05:00
|
|
|
Util::checkDiskSpaceOnRegisteredFileSystems();
|
2016-09-29 09:47:28 -05:00
|
|
|
|
2016-10-17 06:13:47 -05:00
|
|
|
// Request the child to connect to us and add this session.
|
|
|
|
auto sessionsCount = docBroker->addSession(session);
|
|
|
|
Log::trace(docKey + ", ws_sessions++: " + std::to_string(sessionsCount));
|
|
|
|
|
2016-10-14 07:46:49 -05:00
|
|
|
// If its a WOPI host, return time taken to make calls to it
|
|
|
|
const auto storageCallDuration = docBroker->getStorageLoadDuration();
|
|
|
|
if (storageCallDuration != std::chrono::duration<double>::zero())
|
|
|
|
{
|
|
|
|
status = "stats: wopiloadduration " + std::to_string(storageCallDuration.count());
|
|
|
|
Log::trace("Sending to Client [" + status + "].");
|
|
|
|
ws->sendFrame(status.data(), (int) status.size());
|
|
|
|
}
|
|
|
|
|
2016-10-16 11:46:46 -05:00
|
|
|
LOOLWSD::dumpEventTrace(docBroker->getJailId(), id, "NewSession: " + uri);
|
|
|
|
|
2016-10-08 09:31:35 -05:00
|
|
|
// Let messages flow.
|
2016-04-24 11:36:27 -05:00
|
|
|
IoUtil::SocketProcessor(ws,
|
2016-08-31 22:34:41 -05:00
|
|
|
[&session](const std::vector<char>& payload)
|
2016-04-24 11:36:27 -05:00
|
|
|
{
|
2016-08-31 22:34:41 -05:00
|
|
|
return session->handleInput(payload.data(), payload.size());
|
2016-04-24 11:36:27 -05:00
|
|
|
},
|
|
|
|
[&session]() { session->closeFrame(); },
|
2016-08-31 22:34:41 -05:00
|
|
|
[]() { return !!TerminationFlag; });
|
2016-04-24 11:36:27 -05:00
|
|
|
|
2016-10-08 09:31:35 -05:00
|
|
|
// Connection terminated. Destroy session.
|
2016-10-12 22:04:07 -05:00
|
|
|
Log::debug("Client session [" + id + "] terminated. Cleaning up.");
|
2016-01-24 13:48:09 -06:00
|
|
|
{
|
2016-10-16 16:56:25 -05:00
|
|
|
std::unique_lock<std::mutex> DocBrokersLock(DocBrokersMutex);
|
2016-05-09 00:12:13 -05:00
|
|
|
|
2016-10-08 09:31:35 -05:00
|
|
|
// We cannot destroy it, before save, if this is the last session.
|
2016-05-09 00:12:13 -05:00
|
|
|
// Otherwise, we may end up removing the one and only session.
|
|
|
|
bool removedSession = false;
|
2016-07-10 23:50:25 -05:00
|
|
|
|
|
|
|
// We issue a force-save when last editable (non-readonly) session is going away
|
2016-10-08 09:31:35 -05:00
|
|
|
const bool forceSave = docBroker->startDestroy(id);
|
|
|
|
|
2016-05-09 00:12:13 -05:00
|
|
|
sessionsCount = docBroker->getSessionsCount();
|
|
|
|
if (sessionsCount > 1)
|
|
|
|
{
|
|
|
|
sessionsCount = docBroker->removeSession(id);
|
|
|
|
removedSession = true;
|
|
|
|
Log::trace(docKey + ", ws_sessions--: " + std::to_string(sessionsCount));
|
|
|
|
}
|
|
|
|
|
2016-05-08 09:07:17 -05:00
|
|
|
// If we are the last, we must wait for the save to complete.
|
2016-07-10 23:50:25 -05:00
|
|
|
if (forceSave)
|
2016-05-08 09:07:17 -05:00
|
|
|
{
|
2016-07-10 23:50:25 -05:00
|
|
|
Log::info("Shutdown of the last editable (non-readonly) session, saving the document before tearing down.");
|
2016-05-08 09:07:17 -05:00
|
|
|
}
|
2016-04-24 11:36:27 -05:00
|
|
|
|
2016-05-09 00:12:13 -05:00
|
|
|
// We need to wait until the save notification reaches us
|
2016-04-24 11:36:27 -05:00
|
|
|
// and Storage persists the document.
|
2016-07-10 23:50:25 -05:00
|
|
|
if (!docBroker->autoSave(forceSave, COMMAND_TIMEOUT_MS))
|
2016-04-24 11:36:27 -05:00
|
|
|
{
|
|
|
|
Log::error("Auto-save before closing failed.");
|
|
|
|
}
|
2016-05-09 00:12:13 -05:00
|
|
|
|
|
|
|
if (!removedSession)
|
|
|
|
{
|
|
|
|
sessionsCount = docBroker->removeSession(id);
|
|
|
|
Log::trace(docKey + ", ws_sessions--: " + std::to_string(sessionsCount));
|
|
|
|
}
|
2016-04-24 11:36:27 -05:00
|
|
|
}
|
2016-05-09 00:12:13 -05:00
|
|
|
|
|
|
|
if (sessionsCount == 0)
|
|
|
|
{
|
2016-10-16 16:56:25 -05:00
|
|
|
std::unique_lock<std::mutex> DocBrokersLock(DocBrokersMutex);
|
2016-05-09 00:12:13 -05:00
|
|
|
Log::debug("Removing DocumentBroker for docKey [" + docKey + "].");
|
2016-10-16 16:56:25 -05:00
|
|
|
DocBrokers.erase(docKey);
|
2016-05-09 00:12:13 -05:00
|
|
|
}
|
|
|
|
|
2016-08-04 09:31:11 -05:00
|
|
|
LOOLWSD::dumpEventTrace(docBroker->getJailId(), id, "EndSession: " + uri);
|
2016-08-31 22:34:41 -05:00
|
|
|
Log::info("Finishing GET request handler for session [" + id + "].");
|
2016-03-14 10:53:31 -05:00
|
|
|
}
|
2016-10-12 10:15:32 -05:00
|
|
|
catch (const WebSocketErrorMessageException&)
|
|
|
|
{
|
|
|
|
throw;
|
|
|
|
}
|
Attempt to handle unauthorized WOPI usage better
Use the previously unused UnauthorizedRequestException for this, and
throw a such in StorageBase::create() when the WOPI host doesn't match
any of those configured.
In a developer debug build, without access to any real WOPI
functionality, you can test by setting the FAKE_UNAUTHORIZED
environment variable and attempting to edit a plain local file:
URI. That will cause such an exception to be thrown in that function.
Catch that UnauthorizedRequestException in
ClientRequestHandler::handleGetRequest(), and send an 'error:
cmd=internal kind=unauthorized' message to the client. Handle that in
loleaflet in the same place where the 'error: cmd=internal
kild=diskfull' message is handled, and in the same fashion, giving up
on the document.
Actually, using exceptions for relatively non-exceptional situations
like this is lame and makes understanding the code harder, but that is
just my personal preference...
FIXME: By the time StorageBase::create() gets called we have already
sent three 'statusindicator:' messages ('find', 'connect', and
'ready') to the client. We should ideally do the checks we do in
StorageBase::create() much earlier.
Also consider that ClientRequestHandler::handleClientRequest() has
code that catches UnauthorizedRequestException and
BadRequestException, and tries to set the HTTP response in those
cases. I am not sure if that functionality has ever been exercised,
though. Currently, we upgrade the HTTP connection to WebSocket early,
and only after that we check whether the WOPI host is authorized
etc. By that time it is too late to return an HTTP response to the
user. If that even is what we ideally should do? If not, then we
probably should drop the code that constructs HTTP responses and
attempts to send them.
Also, if I, as a test, force an HTTPResponse::HTTP_BAD_REQUEST to be
sent before the HTTP connection is upgraded to WebSocket, loleaflet
throws up the generic "Well, this is embarrassing" dialog anyway. At
least in Firefox on Linux. (Instead of the browser showing some own
dialog, which I was half-expecting to happen.)
2016-10-17 08:55:20 -05:00
|
|
|
catch (const UnauthorizedRequestException& exc)
|
|
|
|
{
|
2016-10-18 01:45:02 -05:00
|
|
|
Log::error("Error in client request handler: " + exc.toString());
|
Attempt to handle unauthorized WOPI usage better
Use the previously unused UnauthorizedRequestException for this, and
throw a such in StorageBase::create() when the WOPI host doesn't match
any of those configured.
In a developer debug build, without access to any real WOPI
functionality, you can test by setting the FAKE_UNAUTHORIZED
environment variable and attempting to edit a plain local file:
URI. That will cause such an exception to be thrown in that function.
Catch that UnauthorizedRequestException in
ClientRequestHandler::handleGetRequest(), and send an 'error:
cmd=internal kind=unauthorized' message to the client. Handle that in
loleaflet in the same place where the 'error: cmd=internal
kild=diskfull' message is handled, and in the same fashion, giving up
on the document.
Actually, using exceptions for relatively non-exceptional situations
like this is lame and makes understanding the code harder, but that is
just my personal preference...
FIXME: By the time StorageBase::create() gets called we have already
sent three 'statusindicator:' messages ('find', 'connect', and
'ready') to the client. We should ideally do the checks we do in
StorageBase::create() much earlier.
Also consider that ClientRequestHandler::handleClientRequest() has
code that catches UnauthorizedRequestException and
BadRequestException, and tries to set the HTTP response in those
cases. I am not sure if that functionality has ever been exercised,
though. Currently, we upgrade the HTTP connection to WebSocket early,
and only after that we check whether the WOPI host is authorized
etc. By that time it is too late to return an HTTP response to the
user. If that even is what we ideally should do? If not, then we
probably should drop the code that constructs HTTP responses and
attempts to send them.
Also, if I, as a test, force an HTTPResponse::HTTP_BAD_REQUEST to be
sent before the HTTP connection is upgraded to WebSocket, loleaflet
throws up the generic "Well, this is embarrassing" dialog anyway. At
least in Firefox on Linux. (Instead of the browser showing some own
dialog, which I was half-expecting to happen.)
2016-10-17 08:55:20 -05:00
|
|
|
status = "error: cmd=internal kind=unauthorized";
|
|
|
|
Log::trace("Sending to Client [" + status + "].");
|
|
|
|
ws->sendFrame(status.data(), (int) status.size());
|
|
|
|
}
|
2016-04-24 11:36:27 -05:00
|
|
|
catch (const std::exception& exc)
|
2016-03-14 10:53:31 -05:00
|
|
|
{
|
2016-04-24 11:36:27 -05:00
|
|
|
Log::error("Error in client request handler: " + std::string(exc.what()));
|
2016-03-14 10:53:31 -05:00
|
|
|
}
|
|
|
|
|
2016-04-18 18:12:26 -05:00
|
|
|
if (session->isCloseFrame())
|
|
|
|
{
|
|
|
|
Log::trace("Normal close handshake.");
|
2016-10-12 05:05:57 -05:00
|
|
|
if (session->shutdownPeer(WebSocket::WS_NORMAL_CLOSE))
|
2016-04-18 18:12:26 -05:00
|
|
|
{
|
|
|
|
// Client initiated close handshake
|
|
|
|
// respond close frame
|
|
|
|
ws->shutdown();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// something wrong, with internal exceptions
|
|
|
|
Log::trace("Abnormal close handshake.");
|
|
|
|
session->closeFrame();
|
2016-10-22 10:34:47 -05:00
|
|
|
// FIXME: handle exception thrown from here ? ...
|
2016-10-12 04:51:06 -05:00
|
|
|
ws->shutdown(WebSocket::WS_ENDPOINT_GOING_AWAY);
|
2016-10-12 05:05:57 -05:00
|
|
|
session->shutdownPeer(WebSocket::WS_ENDPOINT_GOING_AWAY);
|
2016-04-18 18:12:26 -05:00
|
|
|
}
|
2016-10-12 22:04:07 -05:00
|
|
|
|
|
|
|
Log::info("Finished GET request handler for session [" + id + "].");
|
2016-01-24 13:48:09 -06:00
|
|
|
}
|
|
|
|
|
2016-04-16 11:56:23 -05:00
|
|
|
/// Sends back the WOPI Discovery XML.
|
|
|
|
/// The XML needs to be preprocessed to stamp the correct URL etc.
|
|
|
|
/// Returns true if a response has been sent.
|
|
|
|
static bool handleGetWOPIDiscovery(HTTPServerRequest& request, HTTPServerResponse& response)
|
2016-03-15 10:35:59 -05:00
|
|
|
{
|
2016-04-14 07:43:13 -05:00
|
|
|
std::string discoveryPath = Path(Application::instance().commandPath()).parent().toString() + "discovery.xml";
|
|
|
|
if (!File(discoveryPath).exists())
|
|
|
|
{
|
|
|
|
discoveryPath = LOOLWSD_DATADIR "/discovery.xml";
|
|
|
|
}
|
2016-04-16 07:10:52 -05:00
|
|
|
|
2016-03-15 10:35:59 -05:00
|
|
|
const std::string mediaType = "text/xml";
|
|
|
|
const std::string action = "action";
|
|
|
|
const std::string urlsrc = "urlsrc";
|
2016-05-11 09:30:05 -05:00
|
|
|
const auto& config = Application::instance().config();
|
|
|
|
const std::string loleafletHtml = config.getString("loleaflet_html", "loleaflet.html");
|
2016-08-28 14:41:28 -05:00
|
|
|
const std::string uriValue = ((LOOLWSD::isSSLEnabled() || LOOLWSD::isSSLTermination()) ? "https://" : "http://") +
|
2016-04-16 07:10:52 -05:00
|
|
|
(LOOLWSD::ServerName.empty() ? request.getHost() : LOOLWSD::ServerName) +
|
2016-05-11 09:30:05 -05:00
|
|
|
"/loleaflet/" LOOLWSD_VERSION_HASH "/" + loleafletHtml + "?";
|
2016-03-15 10:35:59 -05:00
|
|
|
|
|
|
|
InputSource inputSrc(discoveryPath);
|
2016-04-16 07:10:52 -05:00
|
|
|
DOMParser parser;
|
2016-03-15 10:35:59 -05:00
|
|
|
AutoPtr<Poco::XML::Document> docXML = parser.parse(&inputSrc);
|
|
|
|
AutoPtr<NodeList> listNodes = docXML->getElementsByTagName(action);
|
|
|
|
|
2016-04-16 07:10:52 -05:00
|
|
|
for (unsigned long it = 0; it < listNodes->length(); ++it)
|
2016-03-15 10:35:59 -05:00
|
|
|
{
|
|
|
|
static_cast<Element*>(listNodes->item(it))->setAttribute(urlsrc, uriValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::ostringstream ostrXML;
|
2016-04-16 07:10:52 -05:00
|
|
|
DOMWriter writer;
|
2016-03-15 10:35:59 -05:00
|
|
|
writer.writeNode(ostrXML, docXML);
|
|
|
|
|
|
|
|
response.set("User-Agent", "LOOLWSD WOPI Agent");
|
|
|
|
response.setContentLength(ostrXML.str().length());
|
|
|
|
response.setContentType(mediaType);
|
|
|
|
response.setChunkedTransferEncoding(false);
|
|
|
|
|
|
|
|
std::ostream& ostr = response.send();
|
|
|
|
ostr << ostrXML.str();
|
2016-04-16 07:10:52 -05:00
|
|
|
Log::debug("Sent discovery.xml successfully.");
|
2016-04-16 11:56:23 -05:00
|
|
|
return true;
|
2016-03-15 10:35:59 -05:00
|
|
|
}
|
|
|
|
|
2016-01-24 13:48:09 -06:00
|
|
|
public:
|
|
|
|
|
|
|
|
void handleRequest(HTTPServerRequest& request, HTTPServerResponse& response) override
|
2016-04-07 15:59:27 -05:00
|
|
|
{
|
2016-04-12 15:30:06 -05:00
|
|
|
if (UnitWSD::get().filterHandleRequest(
|
|
|
|
UnitWSD::TestRequest::TEST_REQ_CLIENT,
|
|
|
|
request, response))
|
|
|
|
return;
|
|
|
|
|
2016-09-28 16:03:36 -05:00
|
|
|
#if MAX_CONNECTIONS > 0
|
|
|
|
if (++LOOLWSD::NumConnections > MAX_CONNECTIONS)
|
|
|
|
{
|
|
|
|
--LOOLWSD::NumConnections;
|
|
|
|
Log::error("Maximum number of connections reached.");
|
2016-09-29 08:48:09 -05:00
|
|
|
// accept hand shake
|
|
|
|
WebSocket ws(request, response);
|
2016-10-04 07:10:45 -05:00
|
|
|
shutdownLimitReached(ws);
|
2016-09-28 17:07:50 -05:00
|
|
|
return;
|
2016-09-28 16:03:36 -05:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2016-04-07 15:59:27 -05:00
|
|
|
handleClientRequest(request,response);
|
2016-07-13 21:01:23 -05:00
|
|
|
|
|
|
|
#if MAX_CONNECTIONS > 0
|
|
|
|
--LOOLWSD::NumConnections;
|
|
|
|
#endif
|
2016-04-07 15:59:27 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
static void handleClientRequest(HTTPServerRequest& request, HTTPServerResponse& response)
|
2016-01-24 13:48:09 -06:00
|
|
|
{
|
|
|
|
const auto id = LOOLWSD::GenSessionId();
|
|
|
|
|
2016-06-08 05:21:29 -05:00
|
|
|
Poco::URI requestUri(request.getURI());
|
2016-10-01 04:29:57 -05:00
|
|
|
Log::debug("Handling: " + request.getURI());
|
2016-08-02 09:00:37 -05:00
|
|
|
|
2016-09-29 12:20:52 -05:00
|
|
|
StringTokenizer reqPathTokens(request.getURI(), "/?", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
|
2016-06-08 05:21:29 -05:00
|
|
|
|
2016-04-07 11:31:56 -05:00
|
|
|
Util::setThreadName("client_ws_" + id);
|
2016-02-29 06:25:12 -06:00
|
|
|
|
2016-04-07 07:51:49 -05:00
|
|
|
Log::debug("Thread started.");
|
2016-01-24 13:48:09 -06:00
|
|
|
|
2016-04-16 11:56:23 -05:00
|
|
|
bool responded = false;
|
2016-01-24 13:48:09 -06:00
|
|
|
try
|
|
|
|
{
|
2016-08-28 16:06:15 -05:00
|
|
|
if ((request.getMethod() == HTTPRequest::HTTP_GET || request.getMethod() == HTTPRequest::HTTP_HEAD) && request.getURI() == "/")
|
2016-08-26 06:40:20 -05:00
|
|
|
{
|
|
|
|
std::string mimeType = "text/plain";
|
|
|
|
std::string responseString = "OK";
|
|
|
|
response.setContentLength(responseString.length());
|
|
|
|
response.setContentType(mimeType);
|
|
|
|
response.setChunkedTransferEncoding(false);
|
|
|
|
std::ostream& ostr = response.send();
|
2016-08-28 16:06:15 -05:00
|
|
|
if (request.getMethod() == HTTPRequest::HTTP_GET)
|
|
|
|
{
|
|
|
|
ostr << responseString;
|
|
|
|
}
|
2016-08-26 06:40:20 -05:00
|
|
|
responded = true;
|
|
|
|
}
|
2016-08-28 16:06:15 -05:00
|
|
|
else if (request.getMethod() == HTTPRequest::HTTP_GET && request.getURI() == "/favicon.ico")
|
2016-08-26 05:57:33 -05:00
|
|
|
{
|
|
|
|
std::string mimeType = "image/vnd.microsoft.icon";
|
|
|
|
std::string faviconPath = Path(Application::instance().commandPath()).parent().toString() + "favicon.ico";
|
|
|
|
if (!File(faviconPath).exists())
|
|
|
|
{
|
|
|
|
faviconPath = LOOLWSD_DATADIR "/favicon.ico";
|
|
|
|
}
|
|
|
|
response.setContentType(mimeType);
|
|
|
|
response.sendFile(faviconPath, mimeType);
|
|
|
|
responded = true;
|
|
|
|
}
|
2016-08-28 16:06:15 -05:00
|
|
|
else if (request.getMethod() == HTTPRequest::HTTP_GET && request.getURI() == "/hosting/discovery")
|
2016-03-15 10:35:59 -05:00
|
|
|
{
|
|
|
|
// http://server/hosting/discovery
|
2016-04-16 11:56:23 -05:00
|
|
|
responded = handleGetWOPIDiscovery(request, response);
|
2016-03-15 10:35:59 -05:00
|
|
|
}
|
2016-06-09 03:36:45 -05:00
|
|
|
else if (!(request.find("Upgrade") != request.end() && Poco::icompare(request["Upgrade"], "websocket") == 0) &&
|
2016-09-29 12:20:52 -05:00
|
|
|
reqPathTokens.count() > 0 && reqPathTokens[0] == "lool")
|
2016-01-24 13:48:09 -06:00
|
|
|
{
|
2016-08-30 17:21:50 -05:00
|
|
|
// All post requests have url prefix 'lool'.
|
2016-04-16 11:56:23 -05:00
|
|
|
responded = handlePostRequest(request, response, id);
|
2016-01-24 13:48:09 -06:00
|
|
|
}
|
2016-09-29 12:20:52 -05:00
|
|
|
else if (reqPathTokens.count() > 2 && reqPathTokens[0] == "lool" && reqPathTokens[2] == "ws")
|
2016-01-24 13:48:09 -06:00
|
|
|
{
|
2016-04-16 11:56:23 -05:00
|
|
|
auto ws = std::make_shared<WebSocket>(request, response);
|
2016-08-30 17:21:50 -05:00
|
|
|
responded = true; // After upgrading to WS we should not set HTTP response.
|
2016-04-16 11:56:23 -05:00
|
|
|
try
|
|
|
|
{
|
2016-08-31 22:40:42 -05:00
|
|
|
// First, setup WS options.
|
|
|
|
ws->setBlocking(false);
|
2016-10-14 22:00:33 -05:00
|
|
|
ws->setSendTimeout(WS_SEND_TIMEOUT_MS * 1000);
|
2016-09-29 12:20:52 -05:00
|
|
|
std::string decodedUri;
|
|
|
|
URI::decode(reqPathTokens[1], decodedUri);
|
|
|
|
handleGetRequest(decodedUri, ws, id);
|
2016-04-16 11:56:23 -05:00
|
|
|
}
|
|
|
|
catch (const WebSocketErrorMessageException& exc)
|
|
|
|
{
|
2016-04-24 10:08:08 -05:00
|
|
|
// Internal error that should be passed on to the client.
|
2016-10-18 00:32:18 -05:00
|
|
|
Log::error("ClientRequestHandler::handleClientRequest: WebSocketErrorMessageException: " + exc.toString());
|
2016-04-16 11:56:23 -05:00
|
|
|
try
|
|
|
|
{
|
2016-10-12 10:15:32 -05:00
|
|
|
ws->sendFrame(exc.what(), std::strlen(exc.what()));
|
2016-04-17 07:42:07 -05:00
|
|
|
// abnormal close frame handshake
|
2016-10-12 04:51:06 -05:00
|
|
|
ws->shutdown(WebSocket::WS_ENDPOINT_GOING_AWAY);
|
2016-04-16 11:56:23 -05:00
|
|
|
}
|
|
|
|
catch (const std::exception& exc2)
|
|
|
|
{
|
2016-10-18 00:32:18 -05:00
|
|
|
Log::error("ClientRequestHandler::handleClientRequest: exception while sending WS error message: " + std::string(exc2.what()));
|
2016-04-16 11:56:23 -05:00
|
|
|
}
|
|
|
|
}
|
2016-01-24 13:48:09 -06:00
|
|
|
}
|
2016-06-25 19:10:48 -05:00
|
|
|
else
|
|
|
|
{
|
|
|
|
Log::error("Unknown resource: " + request.getURI());
|
2016-07-07 11:49:58 -05:00
|
|
|
response.setStatusAndReason(HTTPResponse::HTTP_BAD_REQUEST);
|
2016-06-25 19:10:48 -05:00
|
|
|
}
|
2016-01-05 20:29:12 -06:00
|
|
|
}
|
2016-01-06 08:13:21 -06:00
|
|
|
catch (const Exception& exc)
|
2016-01-05 20:29:12 -06:00
|
|
|
{
|
2016-10-18 00:22:46 -05:00
|
|
|
Log::error() << "ClientRequestHandler::handleClientRequest: " << exc.displayText()
|
2016-01-06 08:13:21 -06:00
|
|
|
<< (exc.nested() ? " (" + exc.nested()->displayText() + ")" : "")
|
|
|
|
<< Log::end;
|
2016-04-16 11:56:23 -05:00
|
|
|
response.setStatusAndReason(HTTPResponse::HTTP_SERVICE_UNAVAILABLE);
|
|
|
|
}
|
|
|
|
catch (const UnauthorizedRequestException& exc)
|
|
|
|
{
|
2016-10-18 00:32:18 -05:00
|
|
|
Log::error("ClientRequestHandler::handleClientRequest: UnauthorizedException: " + exc.toString());
|
2016-04-16 11:56:23 -05:00
|
|
|
response.setStatusAndReason(HTTPResponse::HTTP_UNAUTHORIZED);
|
|
|
|
}
|
|
|
|
catch (const BadRequestException& exc)
|
|
|
|
{
|
2016-10-18 00:32:18 -05:00
|
|
|
Log::error("ClientRequestHandler::handleClientRequest: BadRequestException: " + exc.toString());
|
2016-04-16 11:56:23 -05:00
|
|
|
response.setStatusAndReason(HTTPResponse::HTTP_BAD_REQUEST);
|
2016-01-06 08:13:21 -06:00
|
|
|
}
|
|
|
|
catch (const std::exception& exc)
|
|
|
|
{
|
2016-10-18 00:32:18 -05:00
|
|
|
Log::error("ClientRequestHandler::handleClientRequest: Exception: " + std::string(exc.what()));
|
2016-04-16 11:56:23 -05:00
|
|
|
response.setStatusAndReason(HTTPResponse::HTTP_SERVICE_UNAVAILABLE);
|
2016-01-06 08:13:21 -06:00
|
|
|
}
|
2016-04-16 11:56:23 -05:00
|
|
|
|
Attempt to handle unauthorized WOPI usage better
Use the previously unused UnauthorizedRequestException for this, and
throw a such in StorageBase::create() when the WOPI host doesn't match
any of those configured.
In a developer debug build, without access to any real WOPI
functionality, you can test by setting the FAKE_UNAUTHORIZED
environment variable and attempting to edit a plain local file:
URI. That will cause such an exception to be thrown in that function.
Catch that UnauthorizedRequestException in
ClientRequestHandler::handleGetRequest(), and send an 'error:
cmd=internal kind=unauthorized' message to the client. Handle that in
loleaflet in the same place where the 'error: cmd=internal
kild=diskfull' message is handled, and in the same fashion, giving up
on the document.
Actually, using exceptions for relatively non-exceptional situations
like this is lame and makes understanding the code harder, but that is
just my personal preference...
FIXME: By the time StorageBase::create() gets called we have already
sent three 'statusindicator:' messages ('find', 'connect', and
'ready') to the client. We should ideally do the checks we do in
StorageBase::create() much earlier.
Also consider that ClientRequestHandler::handleClientRequest() has
code that catches UnauthorizedRequestException and
BadRequestException, and tries to set the HTTP response in those
cases. I am not sure if that functionality has ever been exercised,
though. Currently, we upgrade the HTTP connection to WebSocket early,
and only after that we check whether the WOPI host is authorized
etc. By that time it is too late to return an HTTP response to the
user. If that even is what we ideally should do? If not, then we
probably should drop the code that constructs HTTP responses and
attempts to send them.
Also, if I, as a test, force an HTTPResponse::HTTP_BAD_REQUEST to be
sent before the HTTP connection is upgraded to WebSocket, loleaflet
throws up the generic "Well, this is embarrassing" dialog anyway. At
least in Firefox on Linux. (Instead of the browser showing some own
dialog, which I was half-expecting to happen.)
2016-10-17 08:55:20 -05:00
|
|
|
if (responded)
|
|
|
|
Log::debug("Already sent response!?");
|
2016-04-16 11:56:23 -05:00
|
|
|
if (!responded)
|
2016-01-06 08:13:21 -06:00
|
|
|
{
|
Attempt to handle unauthorized WOPI usage better
Use the previously unused UnauthorizedRequestException for this, and
throw a such in StorageBase::create() when the WOPI host doesn't match
any of those configured.
In a developer debug build, without access to any real WOPI
functionality, you can test by setting the FAKE_UNAUTHORIZED
environment variable and attempting to edit a plain local file:
URI. That will cause such an exception to be thrown in that function.
Catch that UnauthorizedRequestException in
ClientRequestHandler::handleGetRequest(), and send an 'error:
cmd=internal kind=unauthorized' message to the client. Handle that in
loleaflet in the same place where the 'error: cmd=internal
kild=diskfull' message is handled, and in the same fashion, giving up
on the document.
Actually, using exceptions for relatively non-exceptional situations
like this is lame and makes understanding the code harder, but that is
just my personal preference...
FIXME: By the time StorageBase::create() gets called we have already
sent three 'statusindicator:' messages ('find', 'connect', and
'ready') to the client. We should ideally do the checks we do in
StorageBase::create() much earlier.
Also consider that ClientRequestHandler::handleClientRequest() has
code that catches UnauthorizedRequestException and
BadRequestException, and tries to set the HTTP response in those
cases. I am not sure if that functionality has ever been exercised,
though. Currently, we upgrade the HTTP connection to WebSocket early,
and only after that we check whether the WOPI host is authorized
etc. By that time it is too late to return an HTTP response to the
user. If that even is what we ideally should do? If not, then we
probably should drop the code that constructs HTTP responses and
attempts to send them.
Also, if I, as a test, force an HTTPResponse::HTTP_BAD_REQUEST to be
sent before the HTTP connection is upgraded to WebSocket, loleaflet
throws up the generic "Well, this is embarrassing" dialog anyway. At
least in Firefox on Linux. (Instead of the browser showing some own
dialog, which I was half-expecting to happen.)
2016-10-17 08:55:20 -05:00
|
|
|
// I wonder if this code path has ever been exercised
|
|
|
|
Log::debug("Attempting to send response");
|
2016-04-16 11:56:23 -05:00
|
|
|
response.setContentLength(0);
|
Attempt to handle unauthorized WOPI usage better
Use the previously unused UnauthorizedRequestException for this, and
throw a such in StorageBase::create() when the WOPI host doesn't match
any of those configured.
In a developer debug build, without access to any real WOPI
functionality, you can test by setting the FAKE_UNAUTHORIZED
environment variable and attempting to edit a plain local file:
URI. That will cause such an exception to be thrown in that function.
Catch that UnauthorizedRequestException in
ClientRequestHandler::handleGetRequest(), and send an 'error:
cmd=internal kind=unauthorized' message to the client. Handle that in
loleaflet in the same place where the 'error: cmd=internal
kild=diskfull' message is handled, and in the same fashion, giving up
on the document.
Actually, using exceptions for relatively non-exceptional situations
like this is lame and makes understanding the code harder, but that is
just my personal preference...
FIXME: By the time StorageBase::create() gets called we have already
sent three 'statusindicator:' messages ('find', 'connect', and
'ready') to the client. We should ideally do the checks we do in
StorageBase::create() much earlier.
Also consider that ClientRequestHandler::handleClientRequest() has
code that catches UnauthorizedRequestException and
BadRequestException, and tries to set the HTTP response in those
cases. I am not sure if that functionality has ever been exercised,
though. Currently, we upgrade the HTTP connection to WebSocket early,
and only after that we check whether the WOPI host is authorized
etc. By that time it is too late to return an HTTP response to the
user. If that even is what we ideally should do? If not, then we
probably should drop the code that constructs HTTP responses and
attempts to send them.
Also, if I, as a test, force an HTTPResponse::HTTP_BAD_REQUEST to be
sent before the HTTP connection is upgraded to WebSocket, loleaflet
throws up the generic "Well, this is embarrassing" dialog anyway. At
least in Firefox on Linux. (Instead of the browser showing some own
dialog, which I was half-expecting to happen.)
2016-10-17 08:55:20 -05:00
|
|
|
std::ostream& os = response.send();
|
|
|
|
if (!os.good())
|
|
|
|
Log::debug("Response stream is not good after send");
|
|
|
|
else
|
|
|
|
Log::debug("Response stream *is* good after send");
|
2016-01-05 20:29:12 -06:00
|
|
|
}
|
2015-04-21 07:06:41 -05:00
|
|
|
|
2016-04-07 07:51:49 -05:00
|
|
|
Log::debug("Thread finished.");
|
2016-01-05 20:29:12 -06:00
|
|
|
}
|
|
|
|
};
|
2015-11-25 20:23:08 -06:00
|
|
|
|
2016-01-05 20:29:12 -06:00
|
|
|
/// Handle requests from prisoners (internal).
|
|
|
|
class PrisonerRequestHandler: public HTTPRequestHandler
|
|
|
|
{
|
|
|
|
public:
|
2015-04-21 07:06:41 -05:00
|
|
|
|
2016-01-05 20:29:12 -06:00
|
|
|
void handleRequest(HTTPServerRequest& request, HTTPServerResponse& response) override
|
2016-04-07 15:59:27 -05:00
|
|
|
{
|
2016-04-12 15:30:06 -05:00
|
|
|
if (UnitWSD::get().filterHandleRequest(
|
|
|
|
UnitWSD::TestRequest::TEST_REQ_PRISONER,
|
|
|
|
request, response))
|
|
|
|
return;
|
|
|
|
|
2016-10-21 06:30:02 -05:00
|
|
|
handlePrisonerRequest(request, response);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void handlePrisonerRequest(HTTPServerRequest& request, HTTPServerResponse& response)
|
|
|
|
{
|
2016-10-12 22:39:04 -05:00
|
|
|
Log::trace("Child connection with URI [" + request.getURI() + "].");
|
2016-05-04 06:06:34 -05:00
|
|
|
assert(request.serverAddress().port() == MasterPortNumber);
|
2016-10-12 22:39:04 -05:00
|
|
|
assert(request.getURI().find(NEW_CHILD_URI) == 0);
|
|
|
|
|
|
|
|
// New Child is spawned.
|
|
|
|
const auto params = Poco::URI(request.getURI()).getQueryParameters();
|
|
|
|
Poco::Process::PID pid = -1;
|
|
|
|
for (const auto& param : params)
|
2016-04-03 09:33:35 -05:00
|
|
|
{
|
2016-10-12 22:39:04 -05:00
|
|
|
if (param.first == "pid")
|
2016-04-03 09:33:35 -05:00
|
|
|
{
|
2016-10-12 22:39:04 -05:00
|
|
|
pid = std::stoi(param.second);
|
|
|
|
}
|
|
|
|
else if (param.first == "version")
|
|
|
|
{
|
|
|
|
LOOLWSD::LOKitVersion = param.second;
|
2016-04-03 09:33:35 -05:00
|
|
|
}
|
2016-10-12 22:39:04 -05:00
|
|
|
}
|
2016-04-03 09:33:35 -05:00
|
|
|
|
2016-10-12 22:04:07 -05:00
|
|
|
if (pid <= 0)
|
|
|
|
{
|
|
|
|
Log::error("Invalid PID in child URI [" + request.getURI() + "].");
|
|
|
|
return;
|
|
|
|
}
|
2016-04-03 09:33:35 -05:00
|
|
|
|
2016-10-12 22:04:07 -05:00
|
|
|
Log::info("New child [" + std::to_string(pid) + "].");
|
|
|
|
auto ws = std::make_shared<WebSocket>(request, response);
|
|
|
|
UnitWSD::get().newChild(ws);
|
2016-05-07 08:42:10 -05:00
|
|
|
|
2016-10-12 22:39:04 -05:00
|
|
|
addNewChild(std::make_shared<ChildProcess>(pid, ws));
|
2015-03-04 17:14:04 -06:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-08-13 23:01:13 -05:00
|
|
|
/// External (client) connection handler factory.
|
|
|
|
/// Creates handler objects.
|
2016-03-20 05:59:32 -05:00
|
|
|
class ClientRequestHandlerFactory: public HTTPRequestHandlerFactory
|
2015-03-04 17:14:04 -06:00
|
|
|
{
|
|
|
|
public:
|
2016-07-28 03:24:25 -05:00
|
|
|
ClientRequestHandlerFactory()
|
2016-08-13 23:01:13 -05:00
|
|
|
{
|
|
|
|
}
|
2016-03-20 05:59:32 -05:00
|
|
|
|
2015-03-04 17:14:04 -06:00
|
|
|
HTTPRequestHandler* createRequestHandler(const HTTPServerRequest& request) override
|
|
|
|
{
|
2016-04-07 11:31:56 -05:00
|
|
|
Util::setThreadName("client_req_hdl");
|
2016-01-19 19:38:43 -06:00
|
|
|
|
2015-12-25 11:35:23 -06:00
|
|
|
auto logger = Log::info();
|
|
|
|
logger << "Request from " << request.clientAddress().toString() << ": "
|
|
|
|
<< request.getMethod() << " " << request.getURI() << " "
|
|
|
|
<< request.getVersion();
|
2015-03-07 05:23:46 -06:00
|
|
|
|
2016-04-28 01:43:19 -05:00
|
|
|
for (const auto& it : request)
|
2015-03-04 17:14:04 -06:00
|
|
|
{
|
2016-04-28 01:43:19 -05:00
|
|
|
logger << " / " << it.first << ": " << it.second;
|
2015-03-04 17:14:04 -06:00
|
|
|
}
|
2015-03-07 05:23:46 -06:00
|
|
|
|
2015-12-25 11:35:23 -06:00
|
|
|
logger << Log::end;
|
2016-03-20 05:59:32 -05:00
|
|
|
|
|
|
|
// Routing
|
|
|
|
Poco::URI requestUri(request.getURI());
|
|
|
|
std::vector<std::string> reqPathSegs;
|
|
|
|
requestUri.getPathSegments(reqPathSegs);
|
|
|
|
HTTPRequestHandler* requestHandler;
|
|
|
|
|
|
|
|
// File server
|
|
|
|
if (reqPathSegs.size() >= 1 && reqPathSegs[0] == "loleaflet")
|
|
|
|
{
|
2016-07-28 03:24:25 -05:00
|
|
|
requestHandler = FileServer::createRequestHandler();
|
2016-03-20 05:59:32 -05:00
|
|
|
}
|
|
|
|
// Admin WebSocket Connections
|
2016-06-08 05:21:29 -05:00
|
|
|
else if (reqPathSegs.size() >= 2 && reqPathSegs[0] == "lool" && reqPathSegs[1] == "adminws")
|
2016-03-20 05:59:32 -05:00
|
|
|
{
|
2016-04-06 07:43:44 -05:00
|
|
|
requestHandler = Admin::createRequestHandler();
|
2016-03-20 05:59:32 -05:00
|
|
|
}
|
|
|
|
// Client post and websocket connections
|
|
|
|
else
|
|
|
|
{
|
|
|
|
requestHandler = new ClientRequestHandler();
|
|
|
|
}
|
|
|
|
|
|
|
|
return requestHandler;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-08-13 23:01:13 -05:00
|
|
|
/// Internal (prisoner) connection handler factory.
|
|
|
|
/// Creates handler objects.
|
2016-03-20 05:59:32 -05:00
|
|
|
class PrisonerRequestHandlerFactory: public HTTPRequestHandlerFactory
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
HTTPRequestHandler* createRequestHandler(const HTTPServerRequest& request) override
|
|
|
|
{
|
2016-04-07 11:31:56 -05:00
|
|
|
Util::setThreadName("prsnr_req_hdl");
|
2016-03-20 05:59:32 -05:00
|
|
|
|
|
|
|
auto logger = Log::info();
|
|
|
|
logger << "Request from " << request.clientAddress().toString() << ": "
|
|
|
|
<< request.getMethod() << " " << request.getURI() << " "
|
|
|
|
<< request.getVersion();
|
|
|
|
|
2016-04-28 01:43:19 -05:00
|
|
|
for (const auto& it : request)
|
2016-03-20 05:59:32 -05:00
|
|
|
{
|
2016-04-28 01:43:19 -05:00
|
|
|
logger << " / " << it.first << ": " << it.second;
|
2016-03-20 05:59:32 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
logger << Log::end;
|
|
|
|
return new PrisonerRequestHandler();
|
2015-03-04 17:14:04 -06:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-07-18 06:45:36 -05:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
static inline
|
2016-10-04 07:10:45 -05:00
|
|
|
ServerSocket* getServerSocket(int nClientPortNumber)
|
2016-07-18 06:45:36 -05:00
|
|
|
{
|
2016-10-07 05:46:53 -05:00
|
|
|
try
|
|
|
|
{
|
|
|
|
ServerSocket *socket = LOOLWSD::isSSLEnabled() ? new SecureServerSocket()
|
|
|
|
: new ServerSocket();
|
|
|
|
socket->bind(nClientPortNumber, false);
|
|
|
|
// 64 is the default value for the backlog parameter in Poco when creating a ServerSocket,
|
|
|
|
// so use it here, too.
|
|
|
|
socket->listen(64);
|
|
|
|
return socket;
|
|
|
|
}
|
|
|
|
catch (const Exception& exc)
|
|
|
|
{
|
2016-10-14 04:57:27 -05:00
|
|
|
Log::fatal() << "Could not create server socket: " << exc.displayText() << Log::end;
|
2016-10-07 05:46:53 -05:00
|
|
|
return nullptr;
|
|
|
|
}
|
2016-07-18 06:45:36 -05:00
|
|
|
}
|
|
|
|
|
2016-10-22 10:35:30 -05:00
|
|
|
static inline
|
|
|
|
ServerSocket* findFreeServerPort(int &nClientPortNumber)
|
|
|
|
{
|
|
|
|
ServerSocket *socket = NULL;
|
|
|
|
while (!socket)
|
|
|
|
{
|
|
|
|
socket = getServerSocket(nClientPortNumber);
|
|
|
|
if (!socket)
|
|
|
|
{
|
|
|
|
nClientPortNumber++;
|
|
|
|
Log::info("client port busy - trying " + nClientPortNumber);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return socket;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline
|
|
|
|
ServerSocket* getMasterSocket(int nMasterPortNumber)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
SocketAddress addr2("127.0.0.1", nMasterPortNumber);
|
|
|
|
return new ServerSocket(addr2);
|
|
|
|
}
|
|
|
|
catch (const Exception& exc)
|
|
|
|
{
|
|
|
|
Log::fatal() << "Could not create master socket: " << exc.displayText() << Log::end;
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline
|
|
|
|
ServerSocket* findFreeMasterPort(int &nMasterPortNumber)
|
|
|
|
{
|
|
|
|
ServerSocket *socket = NULL;
|
|
|
|
while (!socket)
|
|
|
|
{
|
|
|
|
socket = getServerSocket(nMasterPortNumber);
|
|
|
|
if (!socket)
|
|
|
|
{
|
|
|
|
nMasterPortNumber++;
|
|
|
|
Log::info("master port busy - trying " + nMasterPortNumber);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return socket;
|
|
|
|
}
|
|
|
|
|
2016-07-18 06:45:36 -05:00
|
|
|
static inline
|
2016-10-04 07:10:45 -05:00
|
|
|
std::string getLaunchURI()
|
2016-07-18 06:45:36 -05:00
|
|
|
{
|
|
|
|
std::string aAbsTopSrcDir = Poco::Path(Application::instance().commandPath()).parent().toString();
|
|
|
|
aAbsTopSrcDir = Poco::Path(aAbsTopSrcDir).absolute().toString();
|
|
|
|
|
|
|
|
std::string aLaunchURI(" ");
|
2016-08-28 14:41:28 -05:00
|
|
|
aLaunchURI += ((LOOLWSD::isSSLEnabled() || LOOLWSD::isSSLTermination()) ? "https://" : "http://");
|
2016-07-18 06:45:36 -05:00
|
|
|
aLaunchURI += LOOLWSD_TEST_HOST ":";
|
|
|
|
aLaunchURI += std::to_string(ClientPortNumber);
|
|
|
|
aLaunchURI += LOOLWSD_TEST_LOLEAFLET_UI;
|
|
|
|
aLaunchURI += "?file_path=file://";
|
|
|
|
aLaunchURI += aAbsTopSrcDir;
|
|
|
|
aLaunchURI += LOOLWSD_TEST_DOCUMENT_RELATIVE_PATH;
|
|
|
|
|
|
|
|
return aLaunchURI;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // anonymous namespace
|
|
|
|
|
2015-12-27 21:47:39 -06:00
|
|
|
std::atomic<unsigned> LOOLWSD::NextSessionId;
|
2016-04-07 03:27:43 -05:00
|
|
|
int LOOLWSD::ForKitWritePipe = -1;
|
2016-08-01 03:05:37 -05:00
|
|
|
bool LOOLWSD::NoCapsForKit = false;
|
2016-01-15 02:40:54 -06:00
|
|
|
std::string LOOLWSD::Cache = LOOLWSD_CACHEDIR;
|
|
|
|
std::string LOOLWSD::SysTemplate;
|
|
|
|
std::string LOOLWSD::LoTemplate;
|
|
|
|
std::string LOOLWSD::ChildRoot;
|
2016-04-14 04:13:30 -05:00
|
|
|
std::string LOOLWSD::ServerName;
|
2016-03-20 09:07:24 -05:00
|
|
|
std::string LOOLWSD::FileServerRoot;
|
2016-06-20 13:58:00 -05:00
|
|
|
std::string LOOLWSD::LOKitVersion;
|
2016-07-19 04:07:07 -05:00
|
|
|
Util::RuntimeConstant<bool> LOOLWSD::SSLEnabled;
|
2016-08-28 14:41:28 -05:00
|
|
|
Util::RuntimeConstant<bool> LOOLWSD::SSLTermination;
|
2016-07-18 06:45:36 -05:00
|
|
|
|
2016-04-08 03:00:58 -05:00
|
|
|
static std::string UnitTestLibrary;
|
2015-07-13 09:13:06 -05:00
|
|
|
|
2016-04-06 22:51:58 -05:00
|
|
|
unsigned int LOOLWSD::NumPreSpawnedChildren = 0;
|
2016-07-13 21:01:23 -05:00
|
|
|
std::atomic<unsigned> LOOLWSD::NumConnections;
|
2016-07-31 12:56:57 -05:00
|
|
|
std::unique_ptr<TraceFileWriter> LOOLWSD::TraceDumper;
|
2015-03-17 18:56:15 -05:00
|
|
|
|
2016-08-13 23:01:13 -05:00
|
|
|
/// Helper class to hold default configuration entries.
|
2016-06-25 07:20:56 -05:00
|
|
|
class AppConfigMap : public Poco::Util::MapConfiguration
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
AppConfigMap(const std::map<std::string, std::string>& map)
|
|
|
|
{
|
|
|
|
for (const auto& pair : map)
|
|
|
|
{
|
|
|
|
setRaw(pair.first, pair.second);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2015-11-25 21:17:08 -06:00
|
|
|
LOOLWSD::LOOLWSD()
|
2015-03-04 17:14:04 -06:00
|
|
|
{
|
2015-03-17 18:56:15 -05:00
|
|
|
}
|
2015-03-07 05:23:46 -06:00
|
|
|
|
2015-03-17 18:56:15 -05:00
|
|
|
LOOLWSD::~LOOLWSD()
|
|
|
|
{
|
|
|
|
}
|
2015-03-04 17:14:04 -06:00
|
|
|
|
2015-03-17 18:56:15 -05:00
|
|
|
void LOOLWSD::initialize(Application& self)
|
|
|
|
{
|
2016-04-16 13:37:38 -05:00
|
|
|
if (geteuid() == 0)
|
|
|
|
{
|
|
|
|
throw std::runtime_error("Do not run as root. Please run as lool user.");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!UnitWSD::init(UnitWSD::UnitType::TYPE_WSD,
|
|
|
|
UnitTestLibrary))
|
|
|
|
{
|
|
|
|
throw std::runtime_error("Failed to load wsd unit test library.");
|
|
|
|
}
|
|
|
|
|
2016-06-25 07:20:56 -05:00
|
|
|
auto& conf = config();
|
|
|
|
|
|
|
|
// Add default values of new entries here.
|
|
|
|
static const std::map<std::string, std::string> DefAppConfig = {
|
2016-06-27 00:37:04 -05:00
|
|
|
{ "tile_cache_path", LOOLWSD_CACHEDIR },
|
2016-06-25 07:20:56 -05:00
|
|
|
{ "sys_template_path", "systemplate" },
|
2016-07-18 10:04:25 -05:00
|
|
|
{ "lo_template_path", "/opt/collaboraoffice5.1" },
|
2016-06-25 07:20:56 -05:00
|
|
|
{ "child_root_path", "jails" },
|
|
|
|
{ "lo_jail_subpath", "lo" },
|
|
|
|
{ "server_name", "" },
|
|
|
|
{ "file_server_root_path", "../loleaflet/../" },
|
|
|
|
{ "num_prespawn_children", "1" },
|
|
|
|
{ "per_document.max_concurrency", "4" },
|
|
|
|
{ "loleaflet_html", "loleaflet.html" },
|
|
|
|
{ "logging.color", "true" },
|
|
|
|
{ "logging.level", "trace" },
|
2016-07-18 06:45:36 -05:00
|
|
|
{ "ssl.enable", "true" },
|
2016-08-28 14:41:28 -05:00
|
|
|
{ "ssl.termination", "true" },
|
2016-06-27 00:37:04 -05:00
|
|
|
{ "ssl.cert_file_path", LOOLWSD_CONFIGDIR "/cert.pem" },
|
|
|
|
{ "ssl.key_file_path", LOOLWSD_CONFIGDIR "/key.pem" },
|
|
|
|
{ "ssl.ca_file_path", LOOLWSD_CONFIGDIR "/ca-chain.cert.pem" },
|
2016-06-25 07:20:56 -05:00
|
|
|
{ "storage.filesystem[@allow]", "false" },
|
|
|
|
{ "storage.wopi[@allow]", "true" },
|
|
|
|
{ "storage.wopi.host[0][@allow]", "true" },
|
|
|
|
{ "storage.wopi.host[0]", "localhost" },
|
|
|
|
{ "storage.wopi.max_file_size", "0" },
|
|
|
|
{ "storage.webdav[@allow]", "false" },
|
2016-08-07 22:32:39 -05:00
|
|
|
{ "logging.file[@enable]", "false" },
|
|
|
|
{ "logging.file.property[0][@name]", "path" },
|
|
|
|
{ "logging.file.property[0]", "loolwsd.log" },
|
|
|
|
{ "logging.file.property[1][@name]", "rotation" },
|
|
|
|
{ "logging.file.property[1]", "never" },
|
|
|
|
{ "logging.file.property[2][@name]", "compress" },
|
|
|
|
{ "logging.file.property[2]", "true" },
|
|
|
|
{ "logging.file.property[3][@name]", "flush" },
|
2016-08-16 04:24:22 -05:00
|
|
|
{ "logging.file.property[3]", "false" },
|
|
|
|
{ "trace[@enable]", "false" }
|
2016-06-25 07:20:56 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
// Set default values, in case they are missing from the config file.
|
|
|
|
AutoPtr<AppConfigMap> pDefConfig(new AppConfigMap(DefAppConfig));
|
|
|
|
conf.addWriteable(pDefConfig, PRIO_SYSTEM); // Lowest priority
|
|
|
|
|
2016-04-06 22:36:54 -05:00
|
|
|
// Load default configuration files, if present.
|
2016-06-25 07:20:56 -05:00
|
|
|
if (loadConfiguration(PRIO_DEFAULT) == 0)
|
2016-03-24 04:37:17 -05:00
|
|
|
{
|
2016-04-06 22:36:54 -05:00
|
|
|
// Fallback to the default path.
|
|
|
|
const std::string configPath = LOOLWSD_CONFIGDIR "/loolwsd.xml";
|
2016-06-25 07:20:56 -05:00
|
|
|
loadConfiguration(configPath, PRIO_DEFAULT);
|
2016-03-24 04:37:17 -05:00
|
|
|
}
|
2016-03-16 14:52:01 -05:00
|
|
|
|
2016-06-25 11:31:22 -05:00
|
|
|
// Override any settings passed on the command-line.
|
|
|
|
AutoPtr<AppConfigMap> pOverrideConfig(new AppConfigMap(_overrideSettings));
|
|
|
|
conf.addWriteable(pOverrideConfig, PRIO_APPLICATION); // Highest priority
|
|
|
|
|
2016-04-16 13:37:38 -05:00
|
|
|
// Allow UT to manipulate before using configuration values.
|
|
|
|
UnitWSD::get().configure(config());
|
|
|
|
|
2016-07-30 21:19:31 -05:00
|
|
|
const auto logLevel = getConfigValue<std::string>(conf, "logging.level", "trace");
|
2016-07-30 10:52:10 -05:00
|
|
|
setenv("LOOL_LOGLEVEL", logLevel.c_str(), true);
|
2016-09-27 06:15:17 -05:00
|
|
|
const auto withColor = getConfigValue<bool>(conf, "logging.color", true) && isatty(fileno(stderr));
|
2016-07-30 10:52:10 -05:00
|
|
|
if (withColor)
|
2016-09-21 17:12:34 -05:00
|
|
|
{
|
2016-07-30 10:52:10 -05:00
|
|
|
setenv("LOOL_LOGCOLOR", "1", true);
|
2016-09-21 17:12:34 -05:00
|
|
|
}
|
2016-08-07 22:32:39 -05:00
|
|
|
|
|
|
|
const auto logToFile = getConfigValue<bool>(conf, "logging.file[@enable]", false);
|
|
|
|
std::map<std::string, std::string> logProperties;
|
|
|
|
for (size_t i = 0; ; ++i)
|
|
|
|
{
|
|
|
|
const std::string confPath = "logging.file.property[" + std::to_string(i) + "]";
|
2016-08-13 07:59:14 -05:00
|
|
|
const auto confName = config().getString(confPath + "[@name]", "");
|
|
|
|
if (!confName.empty())
|
2016-08-07 22:32:39 -05:00
|
|
|
{
|
|
|
|
const auto value = config().getString(confPath, "");
|
2016-08-13 07:59:14 -05:00
|
|
|
logProperties.emplace(confName, value);
|
2016-08-07 22:32:39 -05:00
|
|
|
}
|
|
|
|
else if (!config().has(confPath))
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-21 17:12:34 -05:00
|
|
|
// Setup the logfile envar for the kit processes.
|
|
|
|
if (logToFile)
|
|
|
|
{
|
|
|
|
setenv("LOOL_LOGFILE", "1", true);
|
|
|
|
const auto it = logProperties.find("path");
|
|
|
|
if (it != logProperties.end())
|
|
|
|
{
|
|
|
|
setenv("LOOL_LOGFILENAME", it->second.c_str(), true);
|
2016-10-06 05:37:57 -05:00
|
|
|
#if ENABLE_DEBUG
|
|
|
|
std::cerr << "\nFull log is available in: " << it->second.c_str() << std::endl;
|
|
|
|
#endif
|
2016-09-21 17:12:34 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-07 22:32:39 -05:00
|
|
|
Log::initialize("wsd", logLevel, withColor, logToFile, logProperties);
|
2016-07-30 10:52:10 -05:00
|
|
|
|
2016-07-18 06:45:36 -05:00
|
|
|
#if ENABLE_SSL
|
|
|
|
LOOLWSD::SSLEnabled.set(getConfigValue<bool>(conf, "ssl.enable", true));
|
|
|
|
#else
|
|
|
|
LOOLWSD::SSLEnabled.set(false);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (LOOLWSD::isSSLEnabled())
|
|
|
|
{
|
|
|
|
Log::info("SSL support: SSL is enabled.");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Log::warn("SSL support: SSL is disabled.");
|
|
|
|
}
|
|
|
|
|
2016-09-01 09:24:22 -05:00
|
|
|
#if ENABLE_SSL
|
2016-08-28 14:41:28 -05:00
|
|
|
LOOLWSD::SSLTermination.set(getConfigValue<bool>(conf, "ssl.termination", true));
|
2016-09-01 09:24:22 -05:00
|
|
|
#else
|
|
|
|
LOOLWSD::SSLTermination.set(false);
|
|
|
|
#endif
|
2016-08-28 14:41:28 -05:00
|
|
|
|
2016-06-25 17:03:41 -05:00
|
|
|
Cache = getPathFromConfig("tile_cache_path");
|
|
|
|
SysTemplate = getPathFromConfig("sys_template_path");
|
|
|
|
LoTemplate = getPathFromConfig("lo_template_path");
|
|
|
|
ChildRoot = getPathFromConfig("child_root_path");
|
|
|
|
ServerName = config().getString("server_name");
|
|
|
|
FileServerRoot = getPathFromConfig("file_server_root_path");
|
2016-07-18 06:45:36 -05:00
|
|
|
NumPreSpawnedChildren = getConfigValue<unsigned int>(conf, "num_prespawn_children", 1);
|
2016-04-06 22:51:58 -05:00
|
|
|
|
2016-07-18 06:45:36 -05:00
|
|
|
const auto maxConcurrency = getConfigValue<unsigned int>(conf, "per_document.max_concurrency", 4);
|
2016-06-15 17:46:13 -05:00
|
|
|
if (maxConcurrency > 0)
|
|
|
|
{
|
|
|
|
setenv("MAX_CONCURRENCY", std::to_string(maxConcurrency).c_str(), 1);
|
|
|
|
}
|
|
|
|
|
2016-08-31 16:05:02 -05:00
|
|
|
// Otherwise we profile the soft-device at jail creation time.
|
|
|
|
setenv ("SAL_DISABLE_OPENCL", "true", 1);
|
|
|
|
|
2016-07-13 21:01:23 -05:00
|
|
|
// In Trial Versions we might want to set some limits.
|
|
|
|
LOOLWSD::NumConnections = 0;
|
|
|
|
Log::info() << "Open Documents Limit: " << (MAX_DOCUMENTS > 0 ?
|
|
|
|
std::to_string(MAX_DOCUMENTS) :
|
|
|
|
std::string("unlimited")) << Log::end;
|
|
|
|
|
|
|
|
Log::info() << "Client Connections Limit: " << (MAX_CONNECTIONS > 0 ?
|
|
|
|
std::to_string(MAX_CONNECTIONS) :
|
|
|
|
std::string("unlimited")) << Log::end;
|
|
|
|
|
2016-07-30 21:22:28 -05:00
|
|
|
// Command Tracing.
|
|
|
|
if (getConfigValue<bool>(conf, "trace[@enable]", false))
|
|
|
|
{
|
|
|
|
const auto& path = getConfigValue<std::string>(conf, "trace.path", "");
|
2016-08-04 17:44:24 -05:00
|
|
|
const auto recordOutgoing = getConfigValue<bool>(conf, "trace.outgoing.record", false);
|
|
|
|
std::vector<std::string> filters;
|
|
|
|
for (size_t i = 0; ; ++i)
|
|
|
|
{
|
|
|
|
const std::string confPath = "trace.filter.message[" + std::to_string(i) + "]";
|
|
|
|
const auto regex = config().getString(confPath, "");
|
|
|
|
if (!regex.empty())
|
|
|
|
{
|
|
|
|
filters.push_back(regex);
|
|
|
|
}
|
|
|
|
else if (!config().has(confPath))
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-06 20:23:57 -05:00
|
|
|
const auto compress = getConfigValue<bool>(conf, "trace.path[@compress]", false);
|
|
|
|
TraceDumper.reset(new TraceFileWriter(path, recordOutgoing, compress, filters));
|
2016-07-30 21:22:28 -05:00
|
|
|
Log::info("Command trace dumping enabled to file: " + path);
|
|
|
|
}
|
|
|
|
|
2016-04-16 07:13:59 -05:00
|
|
|
StorageBase::initialize();
|
|
|
|
|
2015-03-17 18:56:15 -05:00
|
|
|
ServerApplication::initialize(self);
|
2016-07-30 21:19:31 -05:00
|
|
|
|
2016-09-27 07:30:45 -05:00
|
|
|
#if ENABLE_DEBUG
|
2016-10-06 05:37:57 -05:00
|
|
|
std::cerr << "\nLaunch this in your browser:\n\n" <<
|
|
|
|
getLaunchURI() << "\n" << std::endl;
|
2016-09-27 07:30:45 -05:00
|
|
|
#endif
|
2015-03-17 18:56:15 -05:00
|
|
|
}
|
2015-03-07 05:23:46 -06:00
|
|
|
|
2016-03-23 06:08:01 -05:00
|
|
|
void LOOLWSD::initializeSSL()
|
|
|
|
{
|
2016-07-18 06:45:36 -05:00
|
|
|
if (!LOOLWSD::isSSLEnabled())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-04-06 17:17:59 -05:00
|
|
|
const auto ssl_cert_file_path = getPathFromConfig("ssl.cert_file_path");
|
2016-03-23 06:08:01 -05:00
|
|
|
Log::info("SSL Cert file: " + ssl_cert_file_path);
|
|
|
|
|
2016-04-06 17:17:59 -05:00
|
|
|
const auto ssl_key_file_path = getPathFromConfig("ssl.key_file_path");
|
2016-03-23 06:08:01 -05:00
|
|
|
Log::info("SSL Key file: " + ssl_key_file_path);
|
|
|
|
|
2016-04-06 17:17:59 -05:00
|
|
|
const auto ssl_ca_file_path = getPathFromConfig("ssl.ca_file_path");
|
2016-03-24 21:42:37 -05:00
|
|
|
Log::info("SSL CA file: " + ssl_ca_file_path);
|
|
|
|
|
2016-03-23 06:08:01 -05:00
|
|
|
Poco::Crypto::initializeCrypto();
|
|
|
|
|
|
|
|
Poco::Net::initializeSSL();
|
|
|
|
Poco::Net::Context::Params sslParams;
|
|
|
|
sslParams.certificateFile = ssl_cert_file_path;
|
|
|
|
sslParams.privateKeyFile = ssl_key_file_path;
|
2016-03-24 21:42:37 -05:00
|
|
|
sslParams.caLocation = ssl_ca_file_path;
|
2016-03-23 06:08:01 -05:00
|
|
|
// Don't ask clients for certificate
|
|
|
|
sslParams.verificationMode = Poco::Net::Context::VERIFY_NONE;
|
|
|
|
|
|
|
|
Poco::SharedPtr<Poco::Net::PrivateKeyPassphraseHandler> consoleHandler = new Poco::Net::KeyConsoleHandler(true);
|
2016-03-30 08:53:27 -05:00
|
|
|
Poco::SharedPtr<Poco::Net::InvalidCertificateHandler> invalidCertHandler = new Poco::Net::ConsoleCertificateHandler(true);
|
2016-03-23 06:08:01 -05:00
|
|
|
|
|
|
|
Poco::Net::Context::Ptr sslContext = new Poco::Net::Context(Poco::Net::Context::SERVER_USE, sslParams);
|
|
|
|
Poco::Net::SSLManager::instance().initializeServer(consoleHandler, invalidCertHandler, sslContext);
|
2016-03-30 10:57:17 -05:00
|
|
|
|
|
|
|
// Init client
|
|
|
|
Poco::Net::Context::Params sslClientParams;
|
|
|
|
// TODO: Be more strict and setup SSL key/certs for owncloud server and us
|
|
|
|
sslClientParams.verificationMode = Poco::Net::Context::VERIFY_NONE;
|
|
|
|
|
|
|
|
Poco::SharedPtr<Poco::Net::PrivateKeyPassphraseHandler> consoleClientHandler = new Poco::Net::KeyConsoleHandler(false);
|
|
|
|
Poco::SharedPtr<Poco::Net::InvalidCertificateHandler> invalidClientCertHandler = new Poco::Net::AcceptCertificateHandler(false);
|
|
|
|
|
|
|
|
Poco::Net::Context::Ptr sslClientContext = new Poco::Net::Context(Poco::Net::Context::CLIENT_USE, sslClientParams);
|
|
|
|
Poco::Net::SSLManager::instance().initializeClient(consoleClientHandler, invalidClientCertHandler, sslClientContext);
|
2016-03-23 06:08:01 -05:00
|
|
|
}
|
|
|
|
|
2015-03-17 18:56:15 -05:00
|
|
|
void LOOLWSD::uninitialize()
|
|
|
|
{
|
|
|
|
ServerApplication::uninitialize();
|
|
|
|
}
|
|
|
|
|
2016-08-17 18:57:38 -05:00
|
|
|
void LOOLWSD::dumpEventTrace(const std::string& pId, const std::string& sessionId, const std::string& data)
|
|
|
|
{
|
|
|
|
if (TraceDumper)
|
|
|
|
{
|
|
|
|
TraceDumper->writeEvent(pId, sessionId, data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void LOOLWSD::dumpIncomingTrace(const std::string& pId, const std::string& sessionId, const std::string& data)
|
|
|
|
{
|
|
|
|
if (TraceDumper)
|
|
|
|
{
|
|
|
|
TraceDumper->writeIncoming(pId, sessionId, data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void LOOLWSD::dumpOutgoingTrace(const std::string& pId, const std::string& sessionId, const std::string& data)
|
|
|
|
{
|
|
|
|
if (TraceDumper)
|
|
|
|
{
|
|
|
|
TraceDumper->writeOutgoing(pId, sessionId, data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-28 04:55:03 -05:00
|
|
|
void LOOLWSD::defineOptions(OptionSet& optionSet)
|
2015-03-17 18:56:15 -05:00
|
|
|
{
|
2015-10-28 04:55:03 -05:00
|
|
|
ServerApplication::defineOptions(optionSet);
|
2015-03-17 18:56:15 -05:00
|
|
|
|
2015-10-28 04:55:03 -05:00
|
|
|
optionSet.addOption(Option("help", "", "Display help information on command line arguments.")
|
|
|
|
.required(false)
|
|
|
|
.repeatable(false));
|
2015-03-17 18:56:15 -05:00
|
|
|
|
2015-12-19 12:38:44 -06:00
|
|
|
optionSet.addOption(Option("version", "", "Display version information.")
|
|
|
|
.required(false)
|
|
|
|
.repeatable(false));
|
|
|
|
|
2015-10-28 04:55:03 -05:00
|
|
|
optionSet.addOption(Option("port", "", "Port number to listen to (default: " + std::to_string(DEFAULT_CLIENT_PORT_NUMBER) + "),"
|
2016-05-04 06:06:34 -05:00
|
|
|
" must not be " + std::to_string(MasterPortNumber) + ".")
|
2015-10-28 04:55:03 -05:00
|
|
|
.required(false)
|
|
|
|
.repeatable(false)
|
|
|
|
.argument("port number"));
|
|
|
|
|
2016-07-18 06:45:36 -05:00
|
|
|
optionSet.addOption(Option("disable-ssl", "", "Disable SSL security layer.")
|
|
|
|
.required(false)
|
|
|
|
.repeatable(false));
|
|
|
|
|
2016-06-25 11:31:22 -05:00
|
|
|
optionSet.addOption(Option("override", "o", "Override any setting by providing fullxmlpath=value.")
|
|
|
|
.required(false)
|
|
|
|
.repeatable(true)
|
|
|
|
.argument("xmlpath"));
|
|
|
|
|
2016-04-12 04:00:33 -05:00
|
|
|
#if ENABLE_DEBUG
|
2016-04-05 11:41:10 -05:00
|
|
|
optionSet.addOption(Option("unitlib", "", "Unit testing library path.")
|
|
|
|
.required(false)
|
|
|
|
.repeatable(false)
|
|
|
|
.argument("unitlib"));
|
2016-04-12 05:48:42 -05:00
|
|
|
|
2016-04-16 14:44:53 -05:00
|
|
|
optionSet.addOption(Option("nocaps", "", "Use a non-privileged forkit for valgrinding.")
|
|
|
|
.required(false)
|
2016-04-18 01:49:21 -05:00
|
|
|
.repeatable(false));
|
2016-04-16 14:44:53 -05:00
|
|
|
|
2016-04-12 05:48:42 -05:00
|
|
|
optionSet.addOption(Option("careerspan", "", "How many seconds to run.")
|
|
|
|
.required(false)
|
|
|
|
.repeatable(false)
|
|
|
|
.argument("seconds"));
|
|
|
|
#endif
|
2015-03-17 18:56:15 -05:00
|
|
|
}
|
|
|
|
|
2016-04-05 11:41:10 -05:00
|
|
|
void LOOLWSD::handleOption(const std::string& optionName,
|
|
|
|
const std::string& value)
|
2015-03-25 07:39:58 -05:00
|
|
|
{
|
2015-10-28 04:55:03 -05:00
|
|
|
ServerApplication::handleOption(optionName, value);
|
2015-03-25 07:39:58 -05:00
|
|
|
|
2015-10-28 04:55:03 -05:00
|
|
|
if (optionName == "help")
|
2015-03-25 07:39:58 -05:00
|
|
|
{
|
|
|
|
displayHelp();
|
2016-03-02 00:47:13 -06:00
|
|
|
std::exit(Application::EXIT_OK);
|
2015-03-25 07:39:58 -05:00
|
|
|
}
|
2015-12-19 12:38:44 -06:00
|
|
|
else if (optionName == "version")
|
2016-04-15 09:07:24 -05:00
|
|
|
DisplayVersion = true;
|
2015-10-28 04:55:03 -05:00
|
|
|
else if (optionName == "port")
|
2015-12-27 16:46:42 -06:00
|
|
|
ClientPortNumber = std::stoi(value);
|
2016-07-18 06:45:36 -05:00
|
|
|
else if (optionName == "disable-ssl")
|
|
|
|
_overrideSettings["ssl.enable"] = "false";
|
2016-06-25 11:31:22 -05:00
|
|
|
else if (optionName == "override")
|
|
|
|
{
|
|
|
|
std::string optName;
|
|
|
|
std::string optValue;
|
|
|
|
LOOLProtocol::parseNameValuePair(value, optName, optValue);
|
|
|
|
_overrideSettings[optName] = optValue;
|
|
|
|
}
|
2016-07-03 09:43:36 -05:00
|
|
|
#if ENABLE_DEBUG
|
|
|
|
else if (optionName == "unitlib")
|
|
|
|
UnitTestLibrary = value;
|
|
|
|
else if (optionName == "nocaps")
|
|
|
|
NoCapsForKit = true;
|
|
|
|
else if (optionName == "careerspan")
|
|
|
|
careerSpanSeconds = std::stoi(value);
|
2016-05-04 06:06:34 -05:00
|
|
|
|
2016-10-12 03:47:26 -05:00
|
|
|
static const char* clientPort = std::getenv("LOOL_TEST_CLIENT_PORT");
|
2016-05-04 06:06:34 -05:00
|
|
|
if (clientPort)
|
|
|
|
ClientPortNumber = std::stoi(clientPort);
|
|
|
|
|
2016-10-12 03:47:26 -05:00
|
|
|
static const char* masterPort = std::getenv("LOOL_TEST_MASTER_PORT");
|
2016-05-04 06:06:34 -05:00
|
|
|
if (masterPort)
|
|
|
|
MasterPortNumber = std::stoi(masterPort);
|
2016-04-12 05:48:42 -05:00
|
|
|
#endif
|
2015-03-25 07:39:58 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void LOOLWSD::displayHelp()
|
|
|
|
{
|
|
|
|
HelpFormatter helpFormatter(options());
|
|
|
|
helpFormatter.setCommand(commandName());
|
|
|
|
helpFormatter.setUsage("OPTIONS");
|
|
|
|
helpFormatter.setHeader("LibreOffice On-Line WebSocket server.");
|
|
|
|
helpFormatter.format(std::cout);
|
|
|
|
}
|
2015-03-17 18:56:15 -05:00
|
|
|
|
2016-04-07 03:27:43 -05:00
|
|
|
Process::PID LOOLWSD::createForKit()
|
2015-07-13 09:13:06 -05:00
|
|
|
{
|
2016-04-03 22:40:56 -05:00
|
|
|
Process::Args args;
|
2015-12-19 19:09:48 -06:00
|
|
|
|
2016-06-27 07:27:08 -05:00
|
|
|
args.push_back("--losubpath=" + std::string(LO_JAIL_SUBPATH));
|
2016-04-03 22:40:56 -05:00
|
|
|
args.push_back("--systemplate=" + SysTemplate);
|
|
|
|
args.push_back("--lotemplate=" + LoTemplate);
|
|
|
|
args.push_back("--childroot=" + ChildRoot);
|
|
|
|
args.push_back("--clientport=" + std::to_string(ClientPortNumber));
|
2016-04-09 11:30:48 -05:00
|
|
|
if (UnitWSD::get().hasKitHooks())
|
|
|
|
args.push_back("--unitlib=" + UnitTestLibrary);
|
2016-04-15 09:07:24 -05:00
|
|
|
if (DisplayVersion)
|
|
|
|
args.push_back("--version");
|
2015-12-19 19:09:48 -06:00
|
|
|
|
2016-04-16 14:44:53 -05:00
|
|
|
std::string forKitPath = Path(Application::instance().commandPath()).parent().toString() + "loolforkit";
|
|
|
|
|
|
|
|
if (NoCapsForKit)
|
|
|
|
{
|
|
|
|
forKitPath = forKitPath + std::string("-nocaps");
|
|
|
|
args.push_back("--nocaps");
|
|
|
|
}
|
2015-12-19 19:09:48 -06:00
|
|
|
|
2016-04-07 03:27:43 -05:00
|
|
|
Log::info("Launching forkit process: " + forKitPath + " " +
|
2016-04-03 22:40:56 -05:00
|
|
|
Poco::cat(std::string(" "), args.begin(), args.end()));
|
2015-12-19 19:09:48 -06:00
|
|
|
|
2016-10-16 16:56:25 -05:00
|
|
|
LastForkRequestTime = std::chrono::steady_clock::now();
|
2016-10-12 10:15:13 -05:00
|
|
|
Pipe inPipe;
|
|
|
|
ProcessHandle child = Process::launch(forKitPath, args, &inPipe, nullptr, nullptr);
|
|
|
|
|
|
|
|
// The Pipe dtor closes the fd, so dup it.
|
|
|
|
ForKitWritePipe = dup(inPipe.writeHandle());
|
2015-12-19 19:09:48 -06:00
|
|
|
|
2016-04-03 22:40:56 -05:00
|
|
|
return child.id();
|
2015-07-13 09:13:06 -05:00
|
|
|
}
|
|
|
|
|
2015-10-16 10:45:03 -05:00
|
|
|
int LOOLWSD::main(const std::vector<std::string>& /*args*/)
|
2015-07-13 09:13:06 -05:00
|
|
|
{
|
2016-07-30 09:59:38 -05:00
|
|
|
Util::setTerminationSignals();
|
|
|
|
Util::setFatalSignals();
|
|
|
|
|
2016-10-07 11:49:37 -05:00
|
|
|
// down-pay all the forkit linking cost once & early.
|
|
|
|
Environment::set("LD_BIND_NOW", "1");
|
|
|
|
|
2016-04-15 09:07:24 -05:00
|
|
|
if (DisplayVersion)
|
2016-06-20 04:51:35 -05:00
|
|
|
{
|
|
|
|
std::string version, hash;
|
|
|
|
Util::getVersionInfo(version, hash);
|
2016-10-06 05:17:36 -05:00
|
|
|
std::cout << "loolwsd version details: " << version << " - " << hash << std::endl;
|
2016-06-20 04:51:35 -05:00
|
|
|
}
|
2016-02-29 06:25:12 -06:00
|
|
|
|
2016-03-23 06:08:01 -05:00
|
|
|
initializeSSL();
|
2016-03-21 03:37:39 -05:00
|
|
|
|
2015-12-29 19:34:53 -06:00
|
|
|
char *locale = setlocale(LC_ALL, nullptr);
|
|
|
|
if (locale == nullptr || std::strcmp(locale, "C") == 0)
|
2015-10-13 12:05:42 -05:00
|
|
|
setlocale(LC_ALL, "en_US.utf8");
|
|
|
|
|
2016-01-15 02:40:54 -06:00
|
|
|
if (access(Cache.c_str(), R_OK | W_OK | X_OK) != 0)
|
2015-07-17 16:55:27 -05:00
|
|
|
{
|
2016-10-14 04:57:27 -05:00
|
|
|
Log::sysfatal("Unable to access cache [" + Cache +
|
2016-04-07 02:36:38 -05:00
|
|
|
"] please make sure it exists, and has write permission for this user.");
|
2016-02-01 19:51:42 -06:00
|
|
|
return Application::EXIT_SOFTWARE;
|
2015-07-17 16:55:27 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// We use the same option set for both parent and child loolwsd,
|
|
|
|
// so must check options required in the parent (but not in the
|
|
|
|
// child) separately now. Also check for options that are
|
|
|
|
// meaningless for the parent.
|
2016-01-15 02:40:54 -06:00
|
|
|
if (SysTemplate.empty())
|
2016-10-14 04:57:27 -05:00
|
|
|
{
|
|
|
|
Log::fatal("Missing --systemplate option");
|
2015-07-17 16:55:27 -05:00
|
|
|
throw MissingOptionException("systemplate");
|
2016-10-14 04:57:27 -05:00
|
|
|
}
|
2016-01-15 02:40:54 -06:00
|
|
|
if (LoTemplate.empty())
|
2016-10-14 04:57:27 -05:00
|
|
|
{
|
|
|
|
Log::fatal("Missing --lotemplate option");
|
2015-07-17 16:55:27 -05:00
|
|
|
throw MissingOptionException("lotemplate");
|
2016-10-14 04:57:27 -05:00
|
|
|
}
|
2016-01-15 02:40:54 -06:00
|
|
|
if (ChildRoot.empty())
|
2016-10-14 04:57:27 -05:00
|
|
|
{
|
|
|
|
Log::fatal("Missing --childroot option");
|
2015-07-17 16:55:27 -05:00
|
|
|
throw MissingOptionException("childroot");
|
2016-10-14 04:57:27 -05:00
|
|
|
}
|
2016-04-18 07:33:22 -05:00
|
|
|
else if (ChildRoot[ChildRoot.size() - 1] != '/')
|
|
|
|
ChildRoot += '/';
|
2015-07-17 16:55:27 -05:00
|
|
|
|
2016-09-30 04:05:36 -05:00
|
|
|
Util::registerFileSystemForDiskSpaceChecks(ChildRoot);
|
|
|
|
Util::registerFileSystemForDiskSpaceChecks(Cache + "/.");
|
2016-09-29 09:47:28 -05:00
|
|
|
|
2016-03-20 09:07:24 -05:00
|
|
|
if (FileServerRoot.empty())
|
2016-06-25 19:10:48 -05:00
|
|
|
FileServerRoot = Poco::Path(Application::instance().commandPath()).parent().parent().toString();
|
2016-04-06 22:38:08 -05:00
|
|
|
FileServerRoot = Poco::Path(FileServerRoot).absolute().toString();
|
|
|
|
Log::debug("FileServerRoot: " + FileServerRoot);
|
2016-03-20 09:07:24 -05:00
|
|
|
|
2016-05-04 06:06:34 -05:00
|
|
|
if (ClientPortNumber == MasterPortNumber)
|
2015-07-17 16:55:27 -05:00
|
|
|
throw IncompatibleOptionsException("port");
|
|
|
|
|
2016-01-31 21:34:18 -06:00
|
|
|
// Configure the Server.
|
2016-05-07 22:28:47 -05:00
|
|
|
// Note: TCPServer internally uses a ThreadPool to
|
|
|
|
// dispatch connections (the default if not given).
|
|
|
|
// The capacity of the ThreadPool is increased here to
|
|
|
|
// match MAX_SESSIONS. The pool must have sufficient available
|
|
|
|
// threads to dispatch new connections, otherwise will deadlock.
|
2016-02-01 19:47:40 -06:00
|
|
|
auto params1 = new HTTPServerParams();
|
|
|
|
params1->setMaxThreads(MAX_SESSIONS);
|
|
|
|
auto params2 = new HTTPServerParams();
|
|
|
|
params2->setMaxThreads(MAX_SESSIONS);
|
2016-01-31 21:34:18 -06:00
|
|
|
|
2015-07-17 13:02:25 -05:00
|
|
|
// Start a server listening on the port for clients
|
2016-07-18 06:45:36 -05:00
|
|
|
|
2016-10-22 10:35:30 -05:00
|
|
|
std::unique_ptr<ServerSocket> psvs(
|
|
|
|
UnitWSD::isUnitTesting() ?
|
|
|
|
findFreeServerPort(ClientPortNumber) :
|
|
|
|
getServerSocket(ClientPortNumber));
|
2016-10-07 05:46:53 -05:00
|
|
|
if (!psvs)
|
|
|
|
return Application::EXIT_SOFTWARE;
|
2016-07-18 06:45:36 -05:00
|
|
|
|
2016-01-31 21:34:18 -06:00
|
|
|
ThreadPool threadPool(NumPreSpawnedChildren*6, MAX_SESSIONS * 2);
|
2016-07-28 03:24:25 -05:00
|
|
|
HTTPServer srv(new ClientRequestHandlerFactory(), threadPool, *psvs, params1);
|
2016-05-12 09:47:05 -05:00
|
|
|
Log::info("Starting master server listening on " + std::to_string(ClientPortNumber));
|
2015-07-17 13:02:25 -05:00
|
|
|
srv.start();
|
|
|
|
|
|
|
|
// And one on the port for child processes
|
2016-05-04 06:06:34 -05:00
|
|
|
SocketAddress addr2("127.0.0.1", MasterPortNumber);
|
2016-10-22 10:35:30 -05:00
|
|
|
std::unique_ptr<ServerSocket> psvs2(
|
|
|
|
UnitWSD::isUnitTesting() ?
|
|
|
|
findFreeMasterPort(MasterPortNumber) :
|
|
|
|
getMasterSocket(MasterPortNumber));
|
|
|
|
if (!psvs2)
|
|
|
|
return Application::EXIT_SOFTWARE;
|
|
|
|
HTTPServer srv2(new PrisonerRequestHandlerFactory(), threadPool, *psvs2, params2);
|
2016-05-12 09:47:05 -05:00
|
|
|
Log::info("Starting prisoner server listening on " + std::to_string(MasterPortNumber));
|
2015-07-17 13:02:25 -05:00
|
|
|
srv2.start();
|
|
|
|
|
2016-05-07 22:28:47 -05:00
|
|
|
// Fire the ForKit process; we are ready.
|
|
|
|
const Process::PID forKitPid = createForKit();
|
|
|
|
if (forKitPid < 0)
|
|
|
|
{
|
2016-10-14 04:57:27 -05:00
|
|
|
Log::fatal("Failed to spawn loolforkit.");
|
2016-05-07 22:28:47 -05:00
|
|
|
return Application::EXIT_SOFTWARE;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Init the Admin manager
|
|
|
|
Admin::instance().setForKitPid(forKitPid);
|
|
|
|
|
|
|
|
// Spawn some children, if necessary.
|
2016-04-04 09:17:03 -05:00
|
|
|
preForkChildren();
|
|
|
|
|
2016-03-22 13:27:38 -05:00
|
|
|
time_t last30SecCheck = time(NULL);
|
|
|
|
|
2016-04-12 05:48:42 -05:00
|
|
|
#if ENABLE_DEBUG
|
2016-04-12 07:32:22 -05:00
|
|
|
time_t startTimeSpan = last30SecCheck;
|
2016-04-12 05:48:42 -05:00
|
|
|
#endif
|
|
|
|
|
2015-12-28 17:24:29 -06:00
|
|
|
int status = 0;
|
2016-04-12 05:32:19 -05:00
|
|
|
while (!TerminationFlag)
|
2015-07-17 13:02:25 -05:00
|
|
|
{
|
2016-04-09 11:30:48 -05:00
|
|
|
UnitWSD::get().invokeTest();
|
2016-04-07 15:59:27 -05:00
|
|
|
|
2016-04-07 03:27:43 -05:00
|
|
|
const pid_t pid = waitpid(forKitPid, &status, WUNTRACED | WNOHANG);
|
2015-07-17 13:02:25 -05:00
|
|
|
if (pid > 0)
|
|
|
|
{
|
2016-04-07 03:27:43 -05:00
|
|
|
if (forKitPid == pid)
|
2015-07-17 13:02:25 -05:00
|
|
|
{
|
2016-08-09 02:14:28 -05:00
|
|
|
if (WIFEXITED(status) == true)
|
2015-07-17 13:02:25 -05:00
|
|
|
{
|
2016-01-14 21:31:02 -06:00
|
|
|
Log::info() << "Child process [" << pid << "] exited with code: "
|
|
|
|
<< WEXITSTATUS(status) << "." << Log::end;
|
|
|
|
|
2016-01-25 19:07:10 -06:00
|
|
|
break;
|
2015-07-17 13:02:25 -05:00
|
|
|
}
|
2016-08-09 02:14:28 -05:00
|
|
|
else if (WIFSIGNALED(status) == true)
|
2016-01-14 21:31:02 -06:00
|
|
|
{
|
|
|
|
std::string fate = "died";
|
|
|
|
if (WCOREDUMP(status))
|
|
|
|
fate = "core-dumped";
|
|
|
|
Log::error() << "Child process [" << pid << "] " << fate
|
|
|
|
<< " with " << Util::signalName(WTERMSIG(status))
|
2016-02-03 16:08:16 -06:00
|
|
|
<< Log::end;
|
2015-07-17 13:02:25 -05:00
|
|
|
|
2016-01-25 19:07:10 -06:00
|
|
|
break;
|
2016-01-14 21:31:02 -06:00
|
|
|
}
|
2016-08-09 02:14:28 -05:00
|
|
|
else if (WIFSTOPPED(status) == true)
|
2016-01-14 21:31:02 -06:00
|
|
|
{
|
|
|
|
Log::info() << "Child process [" << pid << "] stopped with "
|
|
|
|
<< Util::signalName(WSTOPSIG(status))
|
2016-02-03 16:08:16 -06:00
|
|
|
<< Log::end;
|
2016-01-14 21:31:02 -06:00
|
|
|
}
|
2016-08-09 02:14:28 -05:00
|
|
|
else if (WIFCONTINUED(status) == true)
|
2016-01-14 21:31:02 -06:00
|
|
|
{
|
|
|
|
Log::info() << "Child process [" << pid << "] resumed with SIGCONT."
|
|
|
|
<< Log::end;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Log::warn() << "Unknown status returned by waitpid: "
|
|
|
|
<< std::hex << status << "." << Log::end;
|
|
|
|
}
|
2015-07-17 13:02:25 -05:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-03-22 10:58:37 -05:00
|
|
|
Log::error("An unknown child process died, pid: " + std::to_string(pid));
|
2015-07-17 13:02:25 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (pid < 0)
|
2016-02-03 15:43:04 -06:00
|
|
|
{
|
2016-04-07 03:04:05 -05:00
|
|
|
Log::syserror("waitpid failed.");
|
2016-02-03 15:43:04 -06:00
|
|
|
if (errno == ECHILD)
|
|
|
|
{
|
2016-08-30 17:21:50 -05:00
|
|
|
// No child processes.
|
2016-10-14 04:57:27 -05:00
|
|
|
Log::fatal("No Forkit instance. Terminating.");
|
2016-02-03 15:43:04 -06:00
|
|
|
TerminationFlag = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
2016-03-22 10:52:50 -05:00
|
|
|
else // pid == 0, no children have died
|
2015-07-24 13:10:24 -05:00
|
|
|
{
|
2016-04-06 02:15:16 -05:00
|
|
|
if (!std::getenv("LOOL_NO_AUTOSAVE"))
|
2016-03-22 13:27:38 -05:00
|
|
|
{
|
2016-04-09 22:20:20 -05:00
|
|
|
if (time(nullptr) >= last30SecCheck + 30)
|
2016-03-22 13:27:38 -05:00
|
|
|
{
|
2016-04-09 22:20:20 -05:00
|
|
|
try
|
2016-03-22 13:27:38 -05:00
|
|
|
{
|
2016-10-16 16:56:25 -05:00
|
|
|
std::unique_lock<std::mutex> DocBrokersLock(DocBrokersMutex);
|
|
|
|
for (auto& brokerIt : DocBrokers)
|
2016-03-23 11:55:28 -05:00
|
|
|
{
|
2016-05-09 00:11:09 -05:00
|
|
|
brokerIt.second->autoSave(false, 0);
|
2016-03-23 11:55:28 -05:00
|
|
|
}
|
2016-03-22 13:27:38 -05:00
|
|
|
}
|
2016-04-09 22:20:20 -05:00
|
|
|
catch (const std::exception& exc)
|
2016-03-22 13:27:38 -05:00
|
|
|
{
|
2016-04-09 22:20:20 -05:00
|
|
|
Log::error("Exception: " + std::string(exc.what()));
|
2016-03-22 13:27:38 -05:00
|
|
|
}
|
2016-04-09 22:20:20 -05:00
|
|
|
|
|
|
|
last30SecCheck = time(nullptr);
|
2016-03-22 13:27:38 -05:00
|
|
|
}
|
|
|
|
}
|
2016-05-02 21:57:27 -05:00
|
|
|
|
2016-10-14 22:00:33 -05:00
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(CHILD_REBALANCE_INTERVAL_MS));
|
2016-05-02 21:57:27 -05:00
|
|
|
|
|
|
|
// Make sure we have sufficient reserves.
|
|
|
|
prespawnChildren();
|
2015-07-24 13:10:24 -05:00
|
|
|
}
|
2016-04-12 05:48:42 -05:00
|
|
|
#if ENABLE_DEBUG
|
2016-04-12 07:32:22 -05:00
|
|
|
if (careerSpanSeconds > 0 && time(nullptr) > startTimeSpan + careerSpanSeconds)
|
2016-04-12 05:48:42 -05:00
|
|
|
{
|
2016-04-12 07:32:22 -05:00
|
|
|
Log::info(std::to_string(time(nullptr) - startTimeSpan) + " seconds gone, finishing as requested.");
|
2016-04-13 03:43:08 -05:00
|
|
|
TerminationFlag = true;
|
2016-04-12 05:48:42 -05:00
|
|
|
}
|
|
|
|
#endif
|
2015-07-17 13:02:25 -05:00
|
|
|
}
|
2015-04-16 11:15:40 -05:00
|
|
|
|
2015-11-25 20:59:24 -06:00
|
|
|
// stop the service, no more request
|
|
|
|
srv.stop();
|
|
|
|
srv2.stop();
|
2015-12-02 18:15:39 -06:00
|
|
|
|
2015-11-25 20:59:24 -06:00
|
|
|
// close all websockets
|
|
|
|
threadPool.joinAll();
|
|
|
|
|
2015-12-19 19:09:48 -06:00
|
|
|
// Terminate child processes
|
2016-04-07 03:27:43 -05:00
|
|
|
Log::info("Requesting child process " + std::to_string(forKitPid) + " to terminate");
|
|
|
|
Util::requestTermination(forKitPid);
|
2016-10-16 16:56:25 -05:00
|
|
|
for (auto& child : NewChildren)
|
2016-04-17 11:04:23 -05:00
|
|
|
{
|
|
|
|
child->close(true);
|
|
|
|
}
|
2015-12-02 18:15:39 -06:00
|
|
|
|
2016-04-07 03:27:43 -05:00
|
|
|
// Wait for forkit process finish
|
|
|
|
waitpid(forKitPid, &status, WUNTRACED);
|
|
|
|
close(ForKitWritePipe);
|
2016-04-03 22:40:56 -05:00
|
|
|
|
2016-01-15 02:40:54 -06:00
|
|
|
Log::info("Cleaning up childroot directory [" + ChildRoot + "].");
|
2016-01-04 15:15:02 -06:00
|
|
|
std::vector<std::string> jails;
|
2016-01-15 02:40:54 -06:00
|
|
|
File(ChildRoot).list(jails);
|
2016-01-04 15:15:02 -06:00
|
|
|
for (auto& jail : jails)
|
|
|
|
{
|
2016-01-15 02:40:54 -06:00
|
|
|
const auto path = ChildRoot + jail;
|
2016-01-04 15:15:02 -06:00
|
|
|
Log::info("Removing jail [" + path + "].");
|
|
|
|
Util::removeFile(path, true);
|
|
|
|
}
|
2015-12-28 17:24:29 -06:00
|
|
|
|
2016-07-18 06:45:36 -05:00
|
|
|
if (LOOLWSD::isSSLEnabled())
|
|
|
|
{
|
|
|
|
Poco::Net::uninitializeSSL();
|
|
|
|
Poco::Crypto::uninitializeCrypto();
|
|
|
|
}
|
|
|
|
|
2016-03-21 03:37:39 -05:00
|
|
|
|
2016-04-06 13:50:55 -05:00
|
|
|
Log::info("Process [loolwsd] finished.");
|
|
|
|
|
|
|
|
int returnValue = Application::EXIT_OK;
|
2016-04-09 11:30:48 -05:00
|
|
|
UnitWSD::get().returnValue(returnValue);
|
2016-04-06 13:50:55 -05:00
|
|
|
|
|
|
|
return returnValue;
|
2015-03-17 18:56:15 -05:00
|
|
|
}
|
2015-03-04 17:14:04 -06:00
|
|
|
|
2016-04-09 11:30:48 -05:00
|
|
|
void UnitWSD::testHandleRequest(TestRequest type, UnitHTTPServerRequest& request, UnitHTTPServerResponse& response)
|
2016-04-07 15:59:27 -05:00
|
|
|
{
|
|
|
|
switch (type)
|
|
|
|
{
|
|
|
|
case TestRequest::TEST_REQ_CLIENT:
|
|
|
|
ClientRequestHandler::handleClientRequest(request, response);
|
|
|
|
break;
|
|
|
|
case TestRequest::TEST_REQ_PRISONER:
|
2016-10-21 06:30:02 -05:00
|
|
|
PrisonerRequestHandler::handlePrisonerRequest(request, response);
|
2016-04-07 15:59:27 -05:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
assert(false);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-28 14:07:07 -05:00
|
|
|
namespace Util
|
|
|
|
{
|
|
|
|
|
|
|
|
void alertAllUsers(const std::string& cmd, const std::string& kind)
|
|
|
|
{
|
2016-10-16 16:56:25 -05:00
|
|
|
std::lock_guard<std::mutex> DocBrokersLock(DocBrokersMutex);
|
2016-09-28 14:07:07 -05:00
|
|
|
|
2016-10-16 16:56:25 -05:00
|
|
|
for (auto& brokerIt : DocBrokers)
|
2016-09-28 14:07:07 -05:00
|
|
|
{
|
|
|
|
brokerIt.second->alertAllUsersOfDocument(cmd, kind);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2015-03-09 10:34:11 -05:00
|
|
|
POCO_SERVER_MAIN(LOOLWSD)
|
2015-03-04 17:14:04 -06:00
|
|
|
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|