Makefile.am | 1 net/Socket.hpp | 4 net/WebSocketHandler.cpp | 95 +++++++++++++++++ net/WebSocketHandler.hpp | 185 ++++++++++++++++++++++++++++++++++ net/loolnb.cpp | 250 ----------------------------------------------- 5 files changed, 289 insertions(+), 246 deletions(-)
New commits: commit 70a473b081063a04bb22e79e569cb0b19fdf6b88 Author: Jan Holesovsky <[email protected]> Date: Thu Feb 23 17:57:59 2017 +0100 nb: Moved the WebSocketHandler to an own header & cpp. The cpp is there to keep the header free from the POCO dependency. Change-Id: I2202c4ed403084ec1ed40001e10b1ec4fc226f7b diff --git a/Makefile.am b/Makefile.am index e4d4f75..cc5f9f7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -92,6 +92,7 @@ loolwsd_fuzzer_SOURCES = $(loolwsd_sources) \ loolnb_SOURCES = net/loolnb.cpp \ net/Ssl.cpp \ + net/WebSocketHandler.cpp \ common/Log.cpp \ common/Util.cpp diff --git a/net/Socket.hpp b/net/Socket.hpp index 6020ca8..08112c5 100644 --- a/net/Socket.hpp +++ b/net/Socket.hpp @@ -16,9 +16,13 @@ #include <unistd.h> #include <atomic> +#include <cassert> #include <cerrno> #include <cstdlib> #include <cstring> +#include <iostream> +#include <memory> +#include <mutex> #include <sstream> #include <Poco/Net/SocketAddress.h> diff --git a/net/WebSocketHandler.cpp b/net/WebSocketHandler.cpp new file mode 100644 index 0000000..8ad433e --- /dev/null +++ b/net/WebSocketHandler.cpp @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "WebSocketHandler.hpp" + +#include <Poco/MemoryStream.h> +#include <Poco/Net/HTTPRequest.h> +#include <Poco/Net/WebSocket.h> +#include <Poco/StringTokenizer.h> + +void WebSocketHandler::handleWebsocketUpgrade() +{ + int number = 0; + Poco::MemoryInputStream message(&_socket->_inBuffer[0], _socket->_inBuffer.size()); + Poco::Net::HTTPRequest req; + req.read(message); + + // if we succeeded - remove that from our input buffer + // FIXME: We should check if this is GET or POST. For GET, we only + // can have a single request (headers only). For POST, we can/should + // use Poco HTMLForm to parse the post message properly. + // Otherwise, we should catch exceptions from the previous read/parse + // and assume we don't have sufficient data, so we wait some more. + _socket->_inBuffer.clear(); + + Poco::StringTokenizer tokens(req.getURI(), "/?"); + if (tokens.count() == 4) + { + std::string subpool = tokens[2]; + number = std::stoi(tokens[3]); + + // complex algorithmic core: + number = number + 1; + + std::string numberString = std::to_string(number); + std::ostringstream oss; + oss << "HTTP/1.1 200 OK\r\n" + << "Date: Once, Upon a time GMT\r\n" // Mon, 27 Jul 2009 12:28:53 GMT + << "Server: madeup string (Linux)\r\n" + << "Content-Length: " << numberString.size() << "\r\n" + << "Content-Type: text/plain\r\n" + << "Connection: Closed\r\n" + << "\r\n" + << numberString; + ; + std::string str = oss.str(); + _socket->_outBuffer.insert(_socket->_outBuffer.end(), str.begin(), str.end()); + } + else if (tokens.count() == 2 && tokens[1] == "ws") + { // create our websocket goodness ... + _wsVersion = std::stoi(req.get("Sec-WebSocket-Version", "13")); + _wsKey = req.get("Sec-WebSocket-Key", ""); + _wsProtocol = req.get("Sec-WebSocket-Protocol", "chat"); + std::cerr << "version " << _wsVersion << " key '" << _wsKey << "\n"; + // FIXME: other sanity checks ... + + std::ostringstream oss; + oss << "HTTP/1.1 101 Switching Protocols\r\n" + << "Upgrade: websocket\r\n" + << "Connection: Upgrade\r\n" + << "Sec-Websocket-Accept: " << computeAccept(_wsKey) << "\r\n" + << "\r\n"; + std::string str = oss.str(); + _socket->_outBuffer.insert(_socket->_outBuffer.end(), str.begin(), str.end()); + _wsState = WEBSOCKET; + } + else + std::cerr << " unknown tokens " << tokens.count() << std::endl; +} + +namespace { + +/// To make the protected 'computeAccept' accessible. +class PublicComputeAccept : public Poco::Net::WebSocket { +public: + static std::string doComputeAccept(const std::string &key) + { + return computeAccept(key); + } +}; + +} + +std::string WebSocketHandler::computeAccept(const std::string &key) +{ + return PublicComputeAccept::doComputeAccept(key); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/net/WebSocketHandler.hpp b/net/WebSocketHandler.hpp new file mode 100644 index 0000000..286ac53 --- /dev/null +++ b/net/WebSocketHandler.hpp @@ -0,0 +1,185 @@ +/* -*- 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/. + */ + +#ifndef INCLUDED_WEBSOCKETHANDLER_HPP +#define INCLUDED_WEBSOCKETHANDLER_HPP + +#include "Socket.hpp" + +class WebSocketHandler : public SocketHandlerInterface +{ + std::unique_ptr<StreamSocket> _socket; + int _wsVersion; + std::string _wsKey; + std::string _wsProtocol; + std::vector<char> _wsPayload; + enum { HTTP, WEBSOCKET } _wsState; + +public: + WebSocketHandler() : + _wsVersion(0), + _wsState(HTTP) + { + } + + /// Implementation of the SocketHandlerInterface. + virtual void setSocket(StreamSocket* socket) override + { + _socket.reset(socket); + } + + /// Upgrade the http(s) connection to a websocket. + void handleWebsocketUpgrade(); + + enum WSOpCode { + Continuation, // 0x0 + Text, // 0x1 + Binary, // 0x2 + Reserved1, // 0x3 + Reserved2, // 0x4 + Reserved3, // 0x5 + Reserved4, // 0x6 + Reserved5, // 0x7 + Close, // 0x8 + Ping, // 0x9 + Pong // 0xa + // ... reserved + }; + + /// Implementation of the SocketHandlerInterface. + virtual void handleIncomingMessage() override + { + std::cerr << "incoming message with buffer size " << _socket->_inBuffer.size() << "\n"; + if (_wsState == HTTP) + { + handleWebsocketUpgrade(); + return; + } + + // websocket fun ! + size_t len = _socket->_inBuffer.size(); + if (len < 2) // partial read + return; + + unsigned char *p = reinterpret_cast<unsigned char*>(&_socket->_inBuffer[0]); + bool fin = p[0] & 0x80; + WSOpCode code = static_cast<WSOpCode>(p[0] & 0x0f); + bool hasMask = p[1] & 0x80; + size_t payloadLen = p[1] & 0x7f; + size_t headerLen = 2; + + // normally - 7 bit length. + if (payloadLen == 126) // 2 byte length + { + if (len < 2 + 2) + return; + + payloadLen = (((unsigned)p[2]) << 8) | ((unsigned)p[3]); + headerLen += 2; + } + else if (payloadLen == 127) // 8 byte length + { + if (len < 2 + 8) + return; + + payloadLen = ((((uint64_t)(p[9])) << 0) + (((uint64_t)(p[8])) << 8) + + (((uint64_t)(p[7])) << 16) + (((uint64_t)(p[6])) << 24) + + (((uint64_t)(p[5])) << 32) + (((uint64_t)(p[4])) << 40) + + (((uint64_t)(p[3])) << 48) + (((uint64_t)(p[2])) << 56)); + // FIXME: crop read length to remove top / sign bits. + headerLen += 8; + } + + unsigned char *data, *mask; + + if (hasMask) + { + mask = p + headerLen; + headerLen += 4; + } + + if (payloadLen + headerLen > len) + { // partial read wait for more data. + return; + } + + data = p + headerLen; + + if (hasMask) + { + for (size_t i = 0; i < payloadLen; ++i) + data[i] = data[i] ^ mask[i % 4]; + + // FIXME: copy and un-mask at the same time ... + _wsPayload.insert(_wsPayload.end(), data, data + payloadLen); + } else + _wsPayload.insert(_wsPayload.end(), data, data + payloadLen); + + _socket->_inBuffer.erase(_socket->_inBuffer.begin(), _socket->_inBuffer.begin() + headerLen + payloadLen); + + // FIXME: fin, aggregating payloads into _wsPayload etc. + handleMessage(fin, code, _wsPayload); + _wsPayload.clear(); + } + + void sendMessage(const std::vector<char> &data, + WSOpCode code = WSOpCode::Binary) + { + size_t len = data.size(); + bool fin = false; + bool mask = false; + + unsigned char header[2]; + header[0] = (fin ? 0x80 : 0) | static_cast<unsigned char>(code); + header[1] = mask ? 0x80 : 0; + _socket->_outBuffer.push_back((char)header[0]); + + // no out-bound masking ... + if (len < 126) + { + header[1] |= len; + _socket->_outBuffer.push_back((char)header[1]); + } + else if (len <= 0xffff) + { + header[1] |= 126; + _socket->_outBuffer.push_back((char)header[1]); + _socket->_outBuffer.push_back(static_cast<char>((len >> 8) & 0xff)); + _socket->_outBuffer.push_back(static_cast<char>((len >> 0) & 0xff)); + } + else + { + header[1] |= 127; + _socket->_outBuffer.push_back((char)header[1]); + _socket->_outBuffer.push_back(static_cast<char>((len >> 56) & 0xff)); + _socket->_outBuffer.push_back(static_cast<char>((len >> 48) & 0xff)); + _socket->_outBuffer.push_back(static_cast<char>((len >> 40) & 0xff)); + _socket->_outBuffer.push_back(static_cast<char>((len >> 32) & 0xff)); + _socket->_outBuffer.push_back(static_cast<char>((len >> 24) & 0xff)); + _socket->_outBuffer.push_back(static_cast<char>((len >> 16) & 0xff)); + _socket->_outBuffer.push_back(static_cast<char>((len >> 8) & 0xff)); + _socket->_outBuffer.push_back(static_cast<char>((len >> 0) & 0xff)); + } + + // FIXME: pick random number and mask in the outbuffer etc. + assert (!mask); + + _socket->_outBuffer.insert(_socket->_outBuffer.end(), data.begin(), data.end()); + } + + /// To me overriden to handle the websocket messages the way you need. + virtual void handleMessage(bool fin, WSOpCode code, std::vector<char> &data) = 0; + +private: + static std::string computeAccept(const std::string &key); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/net/loolnb.cpp b/net/loolnb.cpp index 0428b4b..201f8a4 100644 --- a/net/loolnb.cpp +++ b/net/loolnb.cpp @@ -25,239 +25,17 @@ #include <Poco/Runnable.h> #include <Poco/Thread.h> -using Poco::MemoryInputStream; -using Poco::StringTokenizer; - #include "Socket.hpp" #include "ServerSocket.hpp" #include "SslSocket.hpp" +#include "WebSocketHandler.hpp" + +using Poco::MemoryInputStream; +using Poco::StringTokenizer; constexpr int HttpPortNumber = 9191; constexpr int SslPortNumber = 9193; -static std::string computeAccept(const std::string &key); - -class WebSocketHandler : public SocketHandlerInterface -{ - std::unique_ptr<StreamSocket> _socket; - int _wsVersion; - std::string _wsKey; - std::string _wsProtocol; - std::vector<char> _wsPayload; - enum { HTTP, WEBSOCKET } _wsState; - -public: - WebSocketHandler() : - _wsVersion(0), - _wsState(HTTP) - { - } - - /// Implementation of the SocketHandlerInterface. - virtual void setSocket(StreamSocket* socket) override - { - _socket.reset(socket); - } - - void handleWebsocketUpgrade() - { - int number = 0; - MemoryInputStream message(&_socket->_inBuffer[0], _socket->_inBuffer.size()); - Poco::Net::HTTPRequest req; - req.read(message); - - // if we succeeded - remove that from our input buffer - // FIXME: We should check if this is GET or POST. For GET, we only - // can have a single request (headers only). For POST, we can/should - // use Poco HTMLForm to parse the post message properly. - // Otherwise, we should catch exceptions from the previous read/parse - // and assume we don't have sufficient data, so we wait some more. - _socket->_inBuffer.clear(); - - StringTokenizer tokens(req.getURI(), "/?"); - if (tokens.count() == 4) - { - std::string subpool = tokens[2]; - number = std::stoi(tokens[3]); - - // complex algorithmic core: - number = number + 1; - - std::string numberString = std::to_string(number); - std::ostringstream oss; - oss << "HTTP/1.1 200 OK\r\n" - << "Date: Once, Upon a time GMT\r\n" // Mon, 27 Jul 2009 12:28:53 GMT - << "Server: madeup string (Linux)\r\n" - << "Content-Length: " << numberString.size() << "\r\n" - << "Content-Type: text/plain\r\n" - << "Connection: Closed\r\n" - << "\r\n" - << numberString; - ; - std::string str = oss.str(); - _socket->_outBuffer.insert(_socket->_outBuffer.end(), str.begin(), str.end()); - } - else if (tokens.count() == 2 && tokens[1] == "ws") - { // create our websocket goodness ... - _wsVersion = std::stoi(req.get("Sec-WebSocket-Version", "13")); - _wsKey = req.get("Sec-WebSocket-Key", ""); - _wsProtocol = req.get("Sec-WebSocket-Protocol", "chat"); - std::cerr << "version " << _wsVersion << " key '" << _wsKey << "\n"; - // FIXME: other sanity checks ... - - std::ostringstream oss; - oss << "HTTP/1.1 101 Switching Protocols\r\n" - << "Upgrade: websocket\r\n" - << "Connection: Upgrade\r\n" - << "Sec-Websocket-Accept: " << computeAccept(_wsKey) << "\r\n" - << "\r\n"; - std::string str = oss.str(); - _socket->_outBuffer.insert(_socket->_outBuffer.end(), str.begin(), str.end()); - _wsState = WEBSOCKET; - } - else - std::cerr << " unknown tokens " << tokens.count() << std::endl; - } - - enum WSOpCode { - Continuation, // 0x0 - Text, // 0x1 - Binary, // 0x2 - Reserved1, // 0x3 - Reserved2, // 0x4 - Reserved3, // 0x5 - Reserved4, // 0x6 - Reserved5, // 0x7 - Close, // 0x8 - Ping, // 0x9 - Pong // 0xa - // ... reserved - }; - - /// Implementation of the SocketHandlerInterface. - virtual void handleIncomingMessage() override - { - std::cerr << "incoming message with buffer size " << _socket->_inBuffer.size() << "\n"; - if (_wsState == HTTP) - { - handleWebsocketUpgrade(); - return; - } - - // websocket fun ! - size_t len = _socket->_inBuffer.size(); - if (len < 2) // partial read - return; - - unsigned char *p = reinterpret_cast<unsigned char*>(&_socket->_inBuffer[0]); - bool fin = p[0] & 0x80; - WSOpCode code = static_cast<WSOpCode>(p[0] & 0x0f); - bool hasMask = p[1] & 0x80; - size_t payloadLen = p[1] & 0x7f; - size_t headerLen = 2; - - // normally - 7 bit length. - if (payloadLen == 126) // 2 byte length - { - if (len < 2 + 2) - return; - - payloadLen = (((unsigned)p[2]) << 8) | ((unsigned)p[3]); - headerLen += 2; - } - else if (payloadLen == 127) // 8 byte length - { - if (len < 2 + 8) - return; - - payloadLen = ((((uint64_t)(p[9])) << 0) + (((uint64_t)(p[8])) << 8) + - (((uint64_t)(p[7])) << 16) + (((uint64_t)(p[6])) << 24) + - (((uint64_t)(p[5])) << 32) + (((uint64_t)(p[4])) << 40) + - (((uint64_t)(p[3])) << 48) + (((uint64_t)(p[2])) << 56)); - // FIXME: crop read length to remove top / sign bits. - headerLen += 8; - } - - unsigned char *data, *mask; - - if (hasMask) - { - mask = p + headerLen; - headerLen += 4; - } - - if (payloadLen + headerLen > len) - { // partial read wait for more data. - return; - } - - data = p + headerLen; - - if (hasMask) - { - for (size_t i = 0; i < payloadLen; ++i) - data[i] = data[i] ^ mask[i % 4]; - - // FIXME: copy and un-mask at the same time ... - _wsPayload.insert(_wsPayload.end(), data, data + payloadLen); - } else - _wsPayload.insert(_wsPayload.end(), data, data + payloadLen); - - _socket->_inBuffer.erase(_socket->_inBuffer.begin(), _socket->_inBuffer.begin() + headerLen + payloadLen); - - // FIXME: fin, aggregating payloads into _wsPayload etc. - handleMessage(fin, code, _wsPayload); - _wsPayload.clear(); - } - - void sendMessage(const std::vector<char> &data, - WSOpCode code = WSOpCode::Binary) - { - size_t len = data.size(); - bool fin = false; - bool mask = false; - - unsigned char header[2]; - header[0] = (fin ? 0x80 : 0) | static_cast<unsigned char>(code); - header[1] = mask ? 0x80 : 0; - _socket->_outBuffer.push_back((char)header[0]); - - // no out-bound masking ... - if (len < 126) - { - header[1] |= len; - _socket->_outBuffer.push_back((char)header[1]); - } - else if (len <= 0xffff) - { - header[1] |= 126; - _socket->_outBuffer.push_back((char)header[1]); - _socket->_outBuffer.push_back(static_cast<char>((len >> 8) & 0xff)); - _socket->_outBuffer.push_back(static_cast<char>((len >> 0) & 0xff)); - } - else - { - header[1] |= 127; - _socket->_outBuffer.push_back((char)header[1]); - _socket->_outBuffer.push_back(static_cast<char>((len >> 56) & 0xff)); - _socket->_outBuffer.push_back(static_cast<char>((len >> 48) & 0xff)); - _socket->_outBuffer.push_back(static_cast<char>((len >> 40) & 0xff)); - _socket->_outBuffer.push_back(static_cast<char>((len >> 32) & 0xff)); - _socket->_outBuffer.push_back(static_cast<char>((len >> 24) & 0xff)); - _socket->_outBuffer.push_back(static_cast<char>((len >> 16) & 0xff)); - _socket->_outBuffer.push_back(static_cast<char>((len >> 8) & 0xff)); - _socket->_outBuffer.push_back(static_cast<char>((len >> 0) & 0xff)); - } - - // FIXME: pick random number and mask in the outbuffer etc. - assert (!mask); - - _socket->_outBuffer.insert(_socket->_outBuffer.end(), data.begin(), data.end()); - } - - virtual void handleMessage(bool fin, WSOpCode code, std::vector<char> &data) = 0; -}; - class SimpleResponseClient : public WebSocketHandler { public: @@ -417,24 +195,4 @@ int main(int argc, const char**argv) return 0; } -// Saves writing this ourselves: - -#include <Poco/Net/WebSocket.h> - -namespace { -#include <Poco/Net/WebSocket.h> - struct Puncture : private Poco::Net::WebSocket { - static std::string doComputeAccept(const std::string &key) - { - return computeAccept(key); - } - }; -} - -static std::string computeAccept(const std::string &key) -{ - return Puncture::doComputeAccept(key); -} - - /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ _______________________________________________ Libreoffice-commits mailing list [email protected] https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits
