/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #if ENABLE_SSL # include #endif SocketPoll DumpSocketPoll("websocket"); // Dumps incoming websocket messages and doesn't respond. class DumpSocketHandler : public WebSocketHandler { public: DumpSocketHandler(const std::weak_ptr& socket, const Poco::Net::HTTPRequest& request) : WebSocketHandler(socket, request) { } private: /// Process incoming websocket messages void handleMessage(const std::vector &data) override { std::cout << "WebSocket message data:\n"; Util::dumpHex(std::cout, "", " ", data, false); } }; /// Handles incoming connections and dispatches to the appropriate handler. class ClientRequestDispatcher : public SimpleSocketHandler { public: ClientRequestDispatcher() { } private: /// Set the socket associated with this ResponseClient. void onConnect(const std::shared_ptr& socket) override { _socket = socket; LOG_TRC("#" << socket->getFD() << " Connected to ClientRequestDispatcher."); } /// Called after successful socket reads. void handleIncomingMessage(SocketDisposition &disposition) override { std::shared_ptr socket = _socket.lock(); std::vector& in = socket->getInBuffer(); 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, true); } 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 reqPathSegs; requestUri.getPathSegments(reqPathSegs); LOG_INF("Incoming websocket request: " << request.getURI()); const std::string& requestURI = request.getURI(); StringVector pathTokens(LOOLProtocol::tokenize(requestURI, '/')); if (request.find("Upgrade") != request.end() && Poco::icompare(request["Upgrade"], "websocket") == 0) { auto dumpHandler = std::make_shared(_socket, request); socket->setHandler(dumpHandler); dumpHandler->sendMessage("version"); dumpHandler->sendMessage("documents"); } else { Poco::Net::HTTPResponse response; response.setStatusAndReason(Poco::Net::HTTPResponse::HTTP_BAD_REQUEST); response.setContentLength(0); LOG_INF("DumpWebSockets bad request"); socket->send(response); disposition.setClosed(); } } catch (const std::exception& exc) { // Bad request. std::ostringstream oss; oss << "HTTP/1.1 400\r\n" << "Date: " << Util::getHttpTimeNow() << "\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 _socket; }; class DumpSocketFactory final : public SocketFactory { private: bool _isSSL = false; public: DumpSocketFactory(bool isSSL) : _isSSL(isSSL) {} std::shared_ptr create(const int physicalFd) override { #if ENABLE_SSL if (_isSSL) return StreamSocket::create(physicalFd, false, std::make_shared()); #else (void) _isSSL; #endif return StreamSocket::create(physicalFd, false, std::make_shared()); } }; namespace Util { void alertAllUsers(const std::string& cmd, const std::string& kind) { std::cout << "error: cmd=" << cmd << " kind=" << kind << std::endl; } } class LoolConfig final: public Poco::Util::XMLConfiguration { public: LoolConfig() {} }; int main (int argc, char **argv) { (void) argc; (void) argv; if (!UnitWSD::init(UnitWSD::UnitType::Wsd, "")) { throw std::runtime_error("Failed to load wsd unit test library."); } Log::initialize("WebSocketDump", "trace", true, false, std::map()); LoolConfig config; config.load("loolwsd.xml"); // read the port & ssl support int port = 9042; bool isSSL = false; std::string monitorAddress = config.getString("monitors.monitor"); if (!monitorAddress.empty()) { Poco::URI monitorURI(monitorAddress); port = monitorURI.getPort(); isSSL = (monitorURI.getScheme() == "wss"); } #if ENABLE_SSL // hard coded but easy for now. const std::string ssl_cert_file_path = "etc/cert.pem"; const std::string ssl_key_file_path = "etc/key.pem"; const std::string ssl_ca_file_path = "etc/ca-chain.cert.pem"; const std::string ssl_cipher_list = "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"; // Initialize the non-blocking socket SSL. if (isSSL) SslContext::initialize(ssl_cert_file_path, ssl_key_file_path, ssl_ca_file_path, ssl_cipher_list); #endif SocketPoll acceptPoll("accept"); // Setup listening socket with a factory for connected sockets. auto serverSocket = std::make_shared( Socket::Type::All, DumpSocketPoll, std::make_shared(isSSL)); 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(100 * 1000); } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */