Makefile.am | 8 + common/UnitHTTP.cpp | 41 -------- common/UnitHTTP.hpp | 29 +++++ tools/Connect.cpp | 11 -- tools/WebSocketDump.cpp | 240 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 276 insertions(+), 53 deletions(-)
New commits: commit f68d54e02a43738754ba82a40ab42644cc6565aa Author: Michael Meeks <michael.me...@collabora.com> Date: Tue Apr 17 14:01:49 2018 +0100 Initial websocket test tool for remote admin connections. Change-Id: I8be2068bf7d77c70720a044556d11f5fc80b2f0c diff --git a/Makefile.am b/Makefile.am index bbff1083e..6aedc561c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2,7 +2,9 @@ SUBDIRS = . test loleaflet export ENABLE_DEBUG -bin_PROGRAMS = loolwsd loolforkit loolmap loolmount looltool loolstress loolconfig +bin_PROGRAMS = \ + loolwsd loolforkit loolmap loolmount \ + looltool loolstress loolconfig loolsocketdump dist_bin_SCRIPTS = loolwsd-systemplate-setup @@ -131,6 +133,9 @@ loolconfig_SOURCES = tools/Config.cpp \ common/Log.cpp \ common/Util.cpp +loolsocketdump_SOURCES = tools/WebSocketDump.cpp \ + $(shared_sources) + wsd_headers = wsd/Admin.hpp \ wsd/AdminModel.hpp \ wsd/Auth.hpp \ diff --git a/tools/Connect.cpp b/tools/Connect.cpp index 04b55ea0c..26db06674 100644 --- a/tools/Connect.cpp +++ b/tools/Connect.cpp @@ -236,13 +236,10 @@ private: namespace Util { - -void alertAllUsers(const std::string& cmd, const std::string& kind) -{ - std::cout << "error: cmd=" << cmd << " kind=" << kind << std::endl; - (void) kind; -} - + void alertAllUsers(const std::string& cmd, const std::string& kind) + { + std::cout << "error: cmd=" << cmd << " kind=" << kind << std::endl; + } } POCO_APP_MAIN(Connect) diff --git a/tools/WebSocketDump.cpp b/tools/WebSocketDump.cpp new file mode 100644 index 000000000..a72e646c4 --- /dev/null +++ b/tools/WebSocketDump.cpp @@ -0,0 +1,240 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +/* A simple tool that accepts web-socket connections and dumps the contents */ + +#include <config.h> + +#include <unistd.h> + +#include <Poco/URI.h> +#include <Poco/MemoryStream.h> +#include <Poco/Net/HTTPRequest.h> +#include <Poco/Net/HTTPResponse.h> +#include <Poco/StringTokenizer.h> + +#include <Log.hpp> +#include <Util.hpp> +#include <Protocol.hpp> +#include <ServerSocket.hpp> +#include <WebSocketHandler.hpp> +#if ENABLE_SSL +# include <SslSocket.hpp> +#endif + +// Dumps incoming websocket messages and doesn't respond. +class DumpSocketHandler : public WebSocketHandler +{ +public: + DumpSocketHandler() + { + } + +private: + /// Process incoming websocket messages + void handleMessage(bool fin, WSOpCode code, std::vector<char> &data) + { + std::cout << "WebSocket message code " << (int)code << " fin " << fin << " data:\n"; + Util::dumpHex(std::cout, "", " ", data, false); + } +}; + +/// Handles incoming connections and dispatches to the appropriate handler. +class ClientRequestDispatcher : public SocketHandlerInterface +{ +public: + ClientRequestDispatcher() + { + } + +private: + + /// Set the socket associated with this ResponseClient. + void onConnect(const std::shared_ptr<StreamSocket>& socket) override + { + _socket = socket; + LOG_TRC("#" << socket->getFD() << " Connected to ClientRequestDispatcher."); + } + + /// Called after successful socket reads. + void handleIncomingMessage(SocketDisposition & /* disposition */) override + { + std::shared_ptr<StreamSocket> socket = _socket.lock(); + std::vector<char>& in = socket->_inBuffer; + LOG_TRC("#" << socket->getFD() << " handling incoming " << in.size() << " bytes."); + + // Find the end of the header, if any. + static const std::string marker("\r\n\r\n"); + auto itBody = std::search(in.begin(), in.end(), + marker.begin(), marker.end()); + if (itBody == in.end()) + { + LOG_DBG("#" << socket->getFD() << " doesn't have enough data yet."); + return; + } + + // Skip the marker. + itBody += marker.size(); + + Poco::MemoryInputStream message(&in[0], in.size()); + Poco::Net::HTTPRequest request; + try + { + request.read(message); + + Log::StreamLogger logger = Log::info(); + if (logger.enabled()) + { + logger << "#" << socket->getFD() << ": Client HTTP Request: " + << request.getMethod() << ' ' + << request.getURI() << ' ' + << request.getVersion(); + + for (const auto& it : request) + { + logger << " / " << it.first << ": " << it.second; + } + + LOG_END(logger); + } + + const std::streamsize contentLength = request.getContentLength(); + const auto offset = itBody - in.begin(); + const std::streamsize available = in.size() - offset; + + if (contentLength != Poco::Net::HTTPMessage::UNKNOWN_CONTENT_LENGTH && available < contentLength) + { + LOG_DBG("Not enough content yet: ContentLength: " << contentLength << ", available: " << available); + return; + } + } + catch (const std::exception& exc) + { + // Probably don't have enough data just yet. + // TODO: timeout if we never get enough. + return; + } + + try + { + // Routing + Poco::URI requestUri(request.getURI()); + std::vector<std::string> reqPathSegs; + requestUri.getPathSegments(reqPathSegs); + + LOG_INF("Incoming websocket request: " << request.getURI()); + + const std::string& requestURI = request.getURI(); + Poco::StringTokenizer pathTokens(requestURI, "/", Poco::StringTokenizer::TOK_IGNORE_EMPTY | + Poco::StringTokenizer::TOK_TRIM); + + if (request.find("Upgrade") != request.end() && Poco::icompare(request["Upgrade"], "websocket") == 0) + socket->setHandler(std::make_shared<DumpSocketHandler>()); + else + { + Poco::Net::HTTPResponse response; + response.setStatusAndReason(Poco::Net::HTTPResponse::HTTP_BAD_REQUEST); + response.setContentLength(0); + LOG_INF("DumpWebSockets bad request"); + socket->send(response); + socket->shutdown(); + } + } + catch (const std::exception& exc) + { + // Bad request. + std::ostringstream oss; + oss << "HTTP/1.1 400\r\n" + << "Date: " << Poco::DateTimeFormatter::format(Poco::Timestamp(), Poco::DateTimeFormat::HTTP_FORMAT) << "\r\n" + << "User-Agent: LOOLWSD WOPI Agent\r\n" + << "Content-Length: 0\r\n" + << "\r\n"; + socket->send(oss.str()); + socket->shutdown(); + + // NOTE: Check _wsState to choose between HTTP response or WebSocket (app-level) error. + LOG_INF("#" << socket->getFD() << " Exception while processing incoming request: [" << + LOOLProtocol::getAbbreviatedMessage(in) << "]: " << exc.what()); + } + + // if we succeeded - remove the request from our input buffer + // we expect one request per socket + in.erase(in.begin(), itBody); + } + + int getPollEvents(std::chrono::steady_clock::time_point /* now */, + int & /* timeoutMaxMs */) override + { + return POLLIN; + } + + void performWrites() override + { + } + +private: + // The socket that owns us (we can't own it). + std::weak_ptr<StreamSocket> _socket; +}; + +class DumpSocketFactory final : public SocketFactory +{ + std::shared_ptr<Socket> create(const int physicalFd) override + { +#if ENABLE_SSL + return StreamSocket::create<SslStreamSocket>(physicalFd, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); +#else + return StreamSocket::create<StreamSocket>(physicalFd, std::unique_ptr<SocketHandlerInterface>{ new ClientRequestDispatcher }); +#endif + } +}; + +namespace Util +{ + void alertAllUsers(const std::string& cmd, const std::string& kind) + { + std::cout << "error: cmd=" << cmd << " kind=" << kind << std::endl; + } +} + +int main (int argc, char **argv) +{ + int port = 9042; + (void) argc; (void) argv; + + SocketPoll acceptPoll("accept"); + SocketPoll DumpSocketPoll("websocket"); + + // Setup listening socket with a factory for connected sockets. + auto serverSocket = std::make_shared<ServerSocket>( + Socket::Type::IPv4, DumpSocketPoll, + std::make_shared<DumpSocketFactory>()); + + if (!serverSocket->bind(ServerSocket::Type::Public, port)) + { + fprintf(stderr, "Failed to bind websocket to port %d\n", port); + return -1; + } + + if (!serverSocket->listen()) + { + fprintf(stderr, "Failed to listen on websocket, port %d\n", port); + return -1; + } + + acceptPoll.startThread(); + acceptPoll.insertNewSocket(serverSocket); + + while (true) + { + DumpSocketPoll.poll(1000); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ commit 81fd84cf4eaefaac7a1412c6bfbfdd271f75e0ec Author: Michael Meeks <michael.me...@collabora.com> Date: Tue Apr 17 16:40:55 2018 +0100 Move UnitHTTP code to header to avoid linking trouble. Change-Id: I430110e840fa8b3862c21c1d4e02288ed704e0a3 diff --git a/Makefile.am b/Makefile.am index ec1faf917..bbff1083e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -61,7 +61,6 @@ shared_sources = common/FileUtil.cpp \ common/SigUtil.cpp \ common/SpookyV2.cpp \ common/Unit.cpp \ - common/UnitHTTP.cpp \ common/Util.cpp \ net/DelaySocket.cpp \ net/Socket.cpp diff --git a/common/UnitHTTP.cpp b/common/UnitHTTP.cpp deleted file mode 100644 index 0d4b23c07..000000000 --- a/common/UnitHTTP.cpp +++ /dev/null @@ -1,41 +0,0 @@ -/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* - * This file is part of the LibreOffice project. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -#include <config.h> - -#include <iostream> -#include "UnitHTTP.hpp" - -Poco::Net::HTTPClientSession *UnitHTTP::createSession() -{ - // HTTP forced in configure hook. - return new Poco::Net::HTTPClientSession ("127.0.0.1", - ClientPortNumber); -} - -UnitWebSocket::UnitWebSocket(const std::string &docURL) -{ - try { - UnitHTTPServerResponse response; - UnitHTTPServerRequest request(response, docURL); - - _session = UnitHTTP::createSession(); - - // FIXME: leaking the session - hey ho ... do we need a UnitSocket ? - _socket = new LOOLWebSocket(*_session, request, response); - } catch (const Poco::Exception &ex) { - std::cerr << "Exception creating websocket " << ex.displayText() << std::endl; - throw; - } -} - -LOOLWebSocket* UnitWebSocket::getLOOLWebSocket() const -{ - return _socket; -} diff --git a/common/UnitHTTP.hpp b/common/UnitHTTP.hpp index a6a107d9f..32f2b7416 100644 --- a/common/UnitHTTP.hpp +++ b/common/UnitHTTP.hpp @@ -114,7 +114,12 @@ public: namespace UnitHTTP { - Poco::Net::HTTPClientSession* createSession(); + Poco::Net::HTTPClientSession* createSession() + { + // HTTP forced in configure hook. + return new Poco::Net::HTTPClientSession ("127.0.0.1", + ClientPortNumber); + } } class UnitWebSocket @@ -124,14 +129,32 @@ class UnitWebSocket public: /// Get a websocket connected for a given URL - UnitWebSocket(const std::string& docURL); + UnitWebSocket(const std::string& docURL) + { + try { + UnitHTTPServerResponse response; + UnitHTTPServerRequest request(response, docURL); + + _session = UnitHTTP::createSession(); + + // FIXME: leaking the session - hey ho ... do we need a UnitSocket ? + _socket = new LOOLWebSocket(*_session, request, response); + } catch (const Poco::Exception &ex) { + std::cerr << "Exception creating websocket " << ex.displayText() << std::endl; + throw; + } + } + ~UnitWebSocket() { delete _socket; delete _session; } - LOOLWebSocket* getLOOLWebSocket() const; + LOOLWebSocket* getLOOLWebSocket() const + { + return _socket; + } }; #endif _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits