From 0d163280f539e91c5a55c2fd679e581be31b177c Mon Sep 17 00:00:00 2001 From: Sadie Powell Date: Mon, 24 May 2021 16:33:11 +0100 Subject: Improve WebSocket header parsing. --- src/modules/m_websocket.cpp | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) (limited to 'src/modules/m_websocket.cpp') diff --git a/src/modules/m_websocket.cpp b/src/modules/m_websocket.cpp index 1b6aa7b8f..bd5058caa 100644 --- a/src/modules/m_websocket.cpp +++ b/src/modules/m_websocket.cpp @@ -29,7 +29,8 @@ #include static const char MagicGUID[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; -static const char whitespace[] = " \t\r\n"; +static const char newline[] = "\r\n"; +static const char whitespace[] = " \t"; static dynamic_reference_nocheck* sha1; struct WebSocketConfig @@ -67,26 +68,39 @@ class WebSocketHook : public IOHookMiddle { class HTTPHeaderFinder { + private: std::string::size_type bpos; std::string::size_type len; public: bool Find(const std::string& req, const char* header, std::string::size_type headerlen, std::string::size_type maxpos) { - std::string::size_type keybegin = req.find(header); - if ((keybegin == std::string::npos) || (keybegin > maxpos) || (keybegin == 0) || (req[keybegin-1] != '\n')) - return false; + // Skip the GET /wibble HTTP/1.1 line. + size_t startpos = req.find(newline) + sizeof(newline) - 1;; + while (startpos < maxpos) + { + size_t endpos = req.find(newline, startpos); + if (req.compare(startpos, headerlen, header)) + { + startpos = endpos + sizeof(newline) - 1; + continue; // Incorrect header. + } - keybegin += headerlen; + bpos = req.find_first_not_of(whitespace, startpos + headerlen); + if (bpos >= endpos) + return false; // Empty value. - bpos = req.find_first_not_of(whitespace, keybegin, sizeof(whitespace)-1); - if ((bpos == std::string::npos) || (bpos > maxpos)) - return false; + size_t epos = std::min(req.find_last_not_of(whitespace), endpos); + if (epos < bpos) + return false; // Should never happen. - const std::string::size_type epos = req.find_first_of(whitespace, bpos, sizeof(whitespace)-1); - len = epos - bpos; + len = epos - bpos; + return true; + } - return true; + // No header found. + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "not found"); + return false; } std::string ExtractValue(const std::string& req) const @@ -405,7 +419,7 @@ class WebSocketHook : public IOHookMiddle key.append(MagicGUID); std::string reply = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "; - reply.append(BinToBase64((*sha1)->GenerateRaw(key), NULL, '=')).append("\r\n\r\n"); + reply.append(BinToBase64((*sha1)->GenerateRaw(key), NULL, '=')).append(newline).append(newline); GetSendQ().push_back(StreamSocket::SendQueue::Element(reply)); SocketEngine::ChangeEventMask(sock, FD_ADD_TRIAL_WRITE); -- cgit v1.3.1-10-gc9f91 From 0b61c19a804d03027bbc0696ba95ea791f28f98d Mon Sep 17 00:00:00 2001 From: Sadie Powell Date: Tue, 17 Aug 2021 22:21:07 +0100 Subject: Minor clean up of the previous commit. --- src/modules/m_websocket.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src/modules/m_websocket.cpp') diff --git a/src/modules/m_websocket.cpp b/src/modules/m_websocket.cpp index bd5058caa..264e295f7 100644 --- a/src/modules/m_websocket.cpp +++ b/src/modules/m_websocket.cpp @@ -76,7 +76,7 @@ class WebSocketHook : public IOHookMiddle bool Find(const std::string& req, const char* header, std::string::size_type headerlen, std::string::size_type maxpos) { // Skip the GET /wibble HTTP/1.1 line. - size_t startpos = req.find(newline) + sizeof(newline) - 1;; + size_t startpos = req.find(newline) + sizeof(newline) - 1; while (startpos < maxpos) { size_t endpos = req.find(newline, startpos); @@ -99,7 +99,6 @@ class WebSocketHook : public IOHookMiddle } // No header found. - ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "not found"); return false; } -- cgit v1.3.1-10-gc9f91 From 1790dacff5966212d12d88a7dc2a169d607a114d Mon Sep 17 00:00:00 2001 From: Sadie Powell Date: Mon, 24 May 2021 16:59:59 +0100 Subject: Implement support for websocket subprotocols. This is based on the current IRCv3 draft but using a vendored protocol. --- docs/conf/modules.conf.example | 3 ++- src/modules/m_websocket.cpp | 25 +++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) (limited to 'src/modules/m_websocket.cpp') diff --git a/docs/conf/modules.conf.example b/docs/conf/modules.conf.example index 041f32f03..f97124a41 100644 --- a/docs/conf/modules.conf.example +++ b/docs/conf/modules.conf.example @@ -2471,7 +2471,8 @@ # WebSocket clients. This is recommended as the WebSocket # protocol requires all text frames to be sent as UTF-8. # If you do not have this enabled messages will be sent as -# binary frames instead. +# binary frames instead. Clients can override this using a +# WebSocket subprotocol. # # diff --git a/src/modules/m_websocket.cpp b/src/modules/m_websocket.cpp index 264e295f7..672c438e9 100644 --- a/src/modules/m_websocket.cpp +++ b/src/modules/m_websocket.cpp @@ -138,6 +138,7 @@ class WebSocketHook : public IOHookMiddle State state; time_t lastpingpong; WebSocketConfig& config; + bool sendastext; static size_t FillHeader(unsigned char* outbuf, size_t sendlength, OpCode opcode) { @@ -398,6 +399,22 @@ class WebSocketHook : public IOHookMiddle } } + std::string selectedproto; + HTTPHeaderFinder protocolheader; + if (protocolheader.Find(recvq, "Sec-WebSocket-Protocol:", 23, reqend)) + { + irc::spacesepstream protostream(protocolheader.ExtractValue(recvq)); + for (std::string proto; protostream.GetToken(proto); ) + { + bool is_text = stdalgo::string::equalsci(proto, "text.inspircd.org"); + if (stdalgo::string::equalsci(proto, "binary.inspircd.org") || is_text) + { + selectedproto = proto; + sendastext = is_text; + break; + } + } + } HTTPHeaderFinder keyheader; if (!keyheader.Find(recvq, "Sec-WebSocket-Key:", 18, reqend)) @@ -418,7 +435,10 @@ class WebSocketHook : public IOHookMiddle key.append(MagicGUID); std::string reply = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "; - reply.append(BinToBase64((*sha1)->GenerateRaw(key), NULL, '=')).append(newline).append(newline); + reply.append(BinToBase64((*sha1)->GenerateRaw(key), NULL, '=')).append(newline); + if (!selectedproto.empty()) + reply.append("Sec-WebSocket-Protocol: ").append(selectedproto).append(newline); + reply.append(newline); GetSendQ().push_back(StreamSocket::SendQueue::Element(reply)); SocketEngine::ChangeEventMask(sock, FD_ADD_TRIAL_WRITE); @@ -434,6 +454,7 @@ class WebSocketHook : public IOHookMiddle , state(STATE_HTTPREQ) , lastpingpong(0) , config(cfg) + , sendastext(cfg.sendastext) { sock->AddIOHook(this); } @@ -454,7 +475,7 @@ class WebSocketHook : public IOHookMiddle if (*chr == '\n') { // We have found an entire message. Send it in its own frame. - if (config.sendastext) + if (sendastext) { // If we send messages as text then we need to ensure they are valid UTF-8. std::string encoded; -- cgit v1.3.1-10-gc9f91