[Libreoffice-commits] online.git: Branch 'private/Ashod/nonblocking' - Makefile.am net/loolnb.cpp net/Socket.hpp net/WebSocketHandler.cpp net/WebSocketHandler.hpp
Jan Holesovsky
kendy at collabora.com
Thu Feb 23 17:02:48 UTC 2017
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 <kendy at collabora.com>
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: */
More information about the Libreoffice-commits
mailing list