summaryrefslogtreecommitdiffstats
path: root/xbmc/network/websocket
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
commitc04dcc2e7d834218ef2d4194331e383402495ae1 (patch)
tree7333e38d10d75386e60f336b80c2443c1166031d /xbmc/network/websocket
parentInitial commit. (diff)
downloadkodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz
kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xbmc/network/websocket')
-rw-r--r--xbmc/network/websocket/CMakeLists.txt11
-rw-r--r--xbmc/network/websocket/WebSocket.cpp430
-rw-r--r--xbmc/network/websocket/WebSocket.h136
-rw-r--r--xbmc/network/websocket/WebSocketManager.cpp77
-rw-r--r--xbmc/network/websocket/WebSocketManager.h19
-rw-r--r--xbmc/network/websocket/WebSocketV13.cpp154
-rw-r--r--xbmc/network/websocket/WebSocketV13.h22
-rw-r--r--xbmc/network/websocket/WebSocketV8.cpp189
-rw-r--r--xbmc/network/websocket/WebSocketV8.h33
9 files changed, 1071 insertions, 0 deletions
diff --git a/xbmc/network/websocket/CMakeLists.txt b/xbmc/network/websocket/CMakeLists.txt
new file mode 100644
index 0000000..306cd6c
--- /dev/null
+++ b/xbmc/network/websocket/CMakeLists.txt
@@ -0,0 +1,11 @@
+set(SOURCES WebSocket.cpp
+ WebSocketManager.cpp
+ WebSocketV13.cpp
+ WebSocketV8.cpp)
+
+set(HEADERS WebSocket.h
+ WebSocketManager.h
+ WebSocketV13.h
+ WebSocketV8.h)
+
+core_add_library(network_websockets)
diff --git a/xbmc/network/websocket/WebSocket.cpp b/xbmc/network/websocket/WebSocket.cpp
new file mode 100644
index 0000000..bbf4a01
--- /dev/null
+++ b/xbmc/network/websocket/WebSocket.cpp
@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "WebSocket.h"
+
+#include "utils/EndianSwap.h"
+#include "utils/HttpParser.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <sstream>
+#include <string>
+
+#define MASK_FIN 0x80
+#define MASK_RSV1 0x40
+#define MASK_RSV2 0x20
+#define MASK_RSV3 0x10
+#define MASK_RSV (MASK_RSV1 | MASK_RSV2 | MASK_RSV3)
+#define MASK_OPCODE 0x0F
+#define MASK_MASK 0x80
+#define MASK_LENGTH 0x7F
+
+#define CONTROL_FRAME 0x08
+
+#define LENGTH_MIN 0x2
+
+CWebSocketFrame::CWebSocketFrame(const char* data, uint64_t length)
+{
+ reset();
+
+ if (data == NULL || length < LENGTH_MIN)
+ return;
+
+ m_free = false;
+ m_data = data;
+ m_lengthFrame = length;
+
+ // Get the FIN flag
+ m_final = ((m_data[0] & MASK_FIN) == MASK_FIN);
+ // Get the RSV1 - RSV3 flags
+ m_extension |= m_data[0] & MASK_RSV1;
+ m_extension |= (m_data[0] & MASK_RSV2) << 1;
+ m_extension |= (m_data[0] & MASK_RSV3) << 2;
+ // Get the opcode
+ m_opcode = (WebSocketFrameOpcode)(m_data[0] & MASK_OPCODE);
+ if (m_opcode >= WebSocketUnknownFrame)
+ {
+ CLog::Log(LOGINFO, "WebSocket: Frame with invalid opcode {:2X} received", m_opcode);
+ reset();
+ return;
+ }
+ if ((m_opcode & CONTROL_FRAME) == CONTROL_FRAME && !m_final)
+ {
+ CLog::Log(LOGINFO, "WebSocket: Fragmented control frame (opcode {:2X}) received", m_opcode);
+ reset();
+ return;
+ }
+
+ // Get the MASK flag
+ m_masked = ((m_data[1] & MASK_MASK) == MASK_MASK);
+
+ // Get the payload length
+ m_length = (uint64_t)(m_data[1] & MASK_LENGTH);
+ if ((m_length <= 125 && m_lengthFrame < m_length + LENGTH_MIN) ||
+ (m_length == 126 && m_lengthFrame < LENGTH_MIN + 2) ||
+ (m_length == 127 && m_lengthFrame < LENGTH_MIN + 8))
+ {
+ CLog::Log(LOGINFO, "WebSocket: Frame with invalid length received");
+ reset();
+ return;
+ }
+
+ if (IsControlFrame() && (m_length > 125 || !m_final))
+ {
+ CLog::Log(LOGWARNING, "WebSocket: Invalid control frame received");
+ reset();
+ return;
+ }
+
+ int offset = 0;
+ if (m_length == 126)
+ {
+ m_length = (uint64_t)Endian_SwapBE16(*(const uint16_t *)(m_data + 2));
+ offset = 2;
+ }
+ else if (m_length == 127)
+ {
+ m_length = Endian_SwapBE64(*(const uint64_t *)(m_data + 2));
+ offset = 8;
+ }
+
+ if (m_lengthFrame < LENGTH_MIN + offset + m_length)
+ {
+ CLog::Log(LOGINFO, "WebSocket: Frame with invalid length received");
+ reset();
+ return;
+ }
+
+ // Get the mask
+ if (m_masked)
+ {
+ m_mask = *(const uint32_t *)(m_data + LENGTH_MIN + offset);
+ offset += 4;
+ }
+
+ if (m_lengthFrame != LENGTH_MIN + offset + m_length)
+ m_lengthFrame = LENGTH_MIN + offset + m_length;
+
+ // Get application data
+ if (m_length > 0)
+ m_applicationData = const_cast<char *>(m_data + LENGTH_MIN + offset);
+ else
+ m_applicationData = NULL;
+
+ // Unmask the application data if necessary
+ if (m_masked)
+ {
+ for (uint64_t index = 0; index < m_length; index++)
+ m_applicationData[index] = m_applicationData[index] ^ ((char *)(&m_mask))[index % 4];
+ }
+
+ m_valid = true;
+}
+
+CWebSocketFrame::CWebSocketFrame(WebSocketFrameOpcode opcode, const char* data /* = NULL */, uint32_t length /* = 0 */,
+ bool final /* = true */, bool masked /* = false */, int32_t mask /* = 0 */, int8_t extension /* = 0 */)
+{
+ reset();
+
+ if (opcode >= WebSocketUnknownFrame)
+ return;
+
+ m_free = true;
+ m_opcode = opcode;
+
+ m_length = length;
+
+ m_masked = masked;
+ m_mask = mask;
+ m_final = final;
+ m_extension = extension;
+
+ std::string buffer;
+ char dataByte = 0;
+
+ // Set the FIN flag
+ if (m_final)
+ dataByte |= MASK_FIN;
+
+ // Set RSV1 - RSV3 flags
+ if (m_extension != 0)
+ dataByte |= (m_extension << 4) & MASK_RSV;
+
+ // Set opcode flag
+ dataByte |= opcode & MASK_OPCODE;
+
+ buffer.push_back(dataByte);
+ dataByte = 0;
+
+ // Set MASK flag
+ if (m_masked)
+ dataByte |= MASK_MASK;
+
+ // Set payload length
+ if (m_length < 126)
+ {
+ dataByte |= m_length & MASK_LENGTH;
+ buffer.push_back(dataByte);
+ }
+ else if (m_length <= 65535)
+ {
+ dataByte |= 126 & MASK_LENGTH;
+ buffer.push_back(dataByte);
+
+ uint16_t dataLength = Endian_SwapBE16((uint16_t)m_length);
+ buffer.append((const char*)&dataLength, 2);
+ }
+ else
+ {
+ dataByte |= 127 & MASK_LENGTH;
+ buffer.push_back(dataByte);
+
+ uint64_t dataLength = Endian_SwapBE64(m_length);
+ buffer.append((const char*)&dataLength, 8);
+ }
+
+ uint64_t applicationDataOffset = 0;
+ if (data)
+ {
+ // Set masking key
+ if (m_masked)
+ {
+ buffer.append((char *)&m_mask, sizeof(m_mask));
+ applicationDataOffset = buffer.size();
+
+ for (uint64_t index = 0; index < m_length; index++)
+ buffer.push_back(data[index] ^ ((char *)(&m_mask))[index % 4]);
+ }
+ else
+ {
+ applicationDataOffset = buffer.size();
+ buffer.append(data, (unsigned int)length);
+ }
+ }
+
+ // Get the whole data
+ m_lengthFrame = buffer.size();
+ m_data = new char[(uint32_t)m_lengthFrame];
+ memcpy(const_cast<char *>(m_data), buffer.c_str(), (uint32_t)m_lengthFrame);
+
+ if (data)
+ {
+ m_applicationData = const_cast<char *>(m_data);
+ m_applicationData += applicationDataOffset;
+ }
+
+ m_valid = true;
+}
+
+CWebSocketFrame::~CWebSocketFrame()
+{
+ if (!m_valid)
+ return;
+
+ if (m_free && m_data != NULL)
+ {
+ delete[] m_data;
+ m_data = NULL;
+ }
+}
+
+void CWebSocketFrame::reset()
+{
+ m_free = false;
+ m_data = NULL;
+ m_lengthFrame = 0;
+ m_length = 0;
+ m_valid = false;
+ m_final = false;
+ m_extension = 0;
+ m_opcode = WebSocketUnknownFrame;
+ m_masked = false;
+ m_mask = 0;
+ m_applicationData = NULL;
+}
+
+CWebSocketMessage::CWebSocketMessage()
+{
+ Clear();
+}
+
+CWebSocketMessage::~CWebSocketMessage()
+{
+ for (unsigned int index = 0; index < m_frames.size(); index++)
+ delete m_frames[index];
+
+ m_frames.clear();
+}
+
+bool CWebSocketMessage::AddFrame(const CWebSocketFrame *frame)
+{
+ if (!frame->IsValid() || m_complete)
+ return false;
+
+ if (frame->IsFinal())
+ m_complete = true;
+ else
+ m_fragmented = true;
+
+ m_frames.push_back(frame);
+
+ return true;
+}
+
+void CWebSocketMessage::Clear()
+{
+ m_fragmented = false;
+ m_complete = false;
+
+ m_frames.clear();
+}
+
+const CWebSocketMessage* CWebSocket::Handle(const char* &buffer, size_t &length, bool &send)
+{
+ send = false;
+
+ while (length > 0)
+ {
+ switch (m_state)
+ {
+ case WebSocketStateConnected:
+ {
+ CWebSocketFrame *frame = GetFrame(buffer, length);
+ if (!frame->IsValid())
+ {
+ CLog::Log(LOGINFO, "WebSocket: Invalid frame received");
+ delete frame;
+ return NULL;
+ }
+
+ // adjust the length and the buffer values
+ length -= (size_t)frame->GetFrameLength();
+ buffer += frame->GetFrameLength();
+
+ if (frame->IsControlFrame())
+ {
+ if (!frame->IsFinal())
+ {
+ delete frame;
+ return NULL;
+ }
+
+ CWebSocketMessage *msg = NULL;
+ switch (frame->GetOpcode())
+ {
+ case WebSocketPing:
+ msg = GetMessage();
+ if (msg != NULL)
+ msg->AddFrame(Pong(frame->GetApplicationData()));
+ break;
+
+ case WebSocketConnectionClose:
+ CLog::Log(LOGINFO, "WebSocket: connection closed by client");
+
+ msg = GetMessage();
+ if (msg != NULL)
+ msg->AddFrame(Close());
+
+ m_state = WebSocketStateClosed;
+ break;
+
+ case WebSocketContinuationFrame:
+ case WebSocketTextFrame:
+ case WebSocketBinaryFrame:
+ case WebSocketPong:
+ case WebSocketUnknownFrame:
+ default:
+ break;
+ }
+
+ delete frame;
+
+ if (msg != NULL)
+ send = true;
+
+ return msg;
+ }
+
+ if (m_message == NULL && (m_message = GetMessage()) == NULL)
+ {
+ CLog::Log(LOGINFO, "WebSocket: Could not allocate a new websocket message");
+ delete frame;
+ return NULL;
+ }
+
+ m_message->AddFrame(frame);
+ if (!m_message->IsComplete())
+ {
+ if (length > 0)
+ continue;
+ else
+ return NULL;
+ }
+
+ CWebSocketMessage *msg = m_message;
+ m_message = NULL;
+ return msg;
+ }
+
+ case WebSocketStateClosing:
+ {
+ CWebSocketFrame *frame = GetFrame(buffer, length);
+
+ if (frame->IsValid())
+ {
+ // adjust the length and the buffer values
+ length -= (size_t)frame->GetFrameLength();
+ buffer += frame->GetFrameLength();
+ }
+
+ if (!frame->IsValid() || frame->GetOpcode() == WebSocketConnectionClose)
+ {
+ CLog::Log(LOGINFO, "WebSocket: Invalid or unexpected frame received (only closing handshake expected)");
+ delete frame;
+ return NULL;
+ }
+
+ m_state = WebSocketStateClosed;
+ return NULL;
+ }
+
+ case WebSocketStateNotConnected:
+ case WebSocketStateClosed:
+ case WebSocketStateHandshaking:
+ default:
+ CLog::Log(LOGINFO, "WebSocket: No frame expected in the current state");
+ return NULL;
+ }
+ }
+
+ return NULL;
+}
+
+const CWebSocketMessage* CWebSocket::Send(WebSocketFrameOpcode opcode, const char* data /* = NULL */, uint32_t length /* = 0 */)
+{
+ CWebSocketFrame *frame = GetFrame(opcode, data, length);
+ if (frame == NULL || !frame->IsValid())
+ {
+ CLog::Log(LOGINFO, "WebSocket: Trying to send an invalid frame");
+ return NULL;
+ }
+
+ CWebSocketMessage *msg = GetMessage();
+ if (msg == NULL)
+ {
+ CLog::Log(LOGINFO, "WebSocket: Could not allocate a message");
+ return NULL;
+ }
+
+ msg->AddFrame(frame);
+ if (msg->IsComplete())
+ return msg;
+
+ return NULL;
+}
diff --git a/xbmc/network/websocket/WebSocket.h b/xbmc/network/websocket/WebSocket.h
new file mode 100644
index 0000000..8901ede
--- /dev/null
+++ b/xbmc/network/websocket/WebSocket.h
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+enum WebSocketFrameOpcode
+{
+ WebSocketContinuationFrame = 0x00,
+ WebSocketTextFrame = 0x01,
+ WebSocketBinaryFrame = 0x02,
+ //0x3 - 0x7 are reserved for non-control frames
+ WebSocketConnectionClose = 0x08,
+ WebSocketPing = 0x09,
+ WebSocketPong = 0x0A,
+ //0xB - 0xF are reserved for control frames
+ WebSocketUnknownFrame = 0x10
+};
+
+enum WebSocketState
+{
+ WebSocketStateNotConnected = 0,
+ WebSocketStateHandshaking = 1,
+ WebSocketStateConnected = 2,
+ WebSocketStateClosing = 3,
+ WebSocketStateClosed = 4
+};
+
+enum WebSocketCloseReason
+{
+ WebSocketCloseNormal = 1000,
+ WebSocketCloseLeaving = 1001,
+ WebSocketCloseProtocolError = 1002,
+ WebSocketCloseInvalidData = 1003,
+ WebSocketCloseFrameTooLarge = 1004,
+ // Reserved status code = 1005,
+ // Reserved status code = 1006,
+ WebSocketCloseInvalidUtf8 = 1007
+};
+
+class CWebSocketFrame
+{
+public:
+ CWebSocketFrame(const char* data, uint64_t length);
+ CWebSocketFrame(WebSocketFrameOpcode opcode, const char* data = NULL, uint32_t length = 0, bool final = true, bool masked = false, int32_t mask = 0, int8_t extension = 0);
+ virtual ~CWebSocketFrame();
+
+ virtual bool IsValid() const { return m_valid; }
+ virtual uint64_t GetFrameLength() const { return m_lengthFrame; }
+ virtual bool IsFinal() const { return m_final; }
+ virtual int8_t GetExtension() const { return m_extension; }
+ virtual WebSocketFrameOpcode GetOpcode() const { return m_opcode; }
+ virtual bool IsControlFrame() const { return (m_valid && (m_opcode & 0x8) == 0x8); }
+ virtual bool IsMasked() const { return m_masked; }
+ virtual uint64_t GetLength() const { return m_length; }
+ virtual int32_t GetMask() const { return m_mask; }
+ virtual const char* GetFrameData() const { return m_data; }
+ virtual const char* GetApplicationData() const { return m_applicationData; }
+
+protected:
+ bool m_free;
+ const char *m_data;
+ uint64_t m_lengthFrame;
+ uint64_t m_length;
+ bool m_valid;
+ bool m_final;
+ int8_t m_extension;
+ WebSocketFrameOpcode m_opcode;
+ bool m_masked;
+ int32_t m_mask;
+ char *m_applicationData;
+
+private:
+ void reset();
+ CWebSocketFrame(const CWebSocketFrame&) = delete;
+ CWebSocketFrame& operator=(const CWebSocketFrame&) = delete;
+};
+
+class CWebSocketMessage
+{
+public:
+ CWebSocketMessage();
+ virtual ~CWebSocketMessage();
+
+ virtual bool IsFragmented() const { return m_fragmented; }
+ virtual bool IsComplete() const { return m_complete; }
+
+ virtual bool AddFrame(const CWebSocketFrame* frame);
+ virtual const std::vector<const CWebSocketFrame *>& GetFrames() const { return m_frames; }
+
+ virtual void Clear();
+
+protected:
+ std::vector<const CWebSocketFrame *> m_frames;
+ bool m_fragmented;
+ bool m_complete;
+};
+
+class CWebSocket
+{
+public:
+ CWebSocket() { m_state = WebSocketStateNotConnected; m_message = NULL; }
+ virtual ~CWebSocket()
+ {
+ if (m_message)
+ delete m_message;
+ }
+
+ int GetVersion() { return m_version; }
+ WebSocketState GetState() { return m_state; }
+
+ virtual bool Handshake(const char* data, size_t length, std::string &response) = 0;
+ virtual const CWebSocketMessage* Handle(const char* &buffer, size_t &length, bool &send);
+ virtual const CWebSocketMessage* Send(WebSocketFrameOpcode opcode, const char* data = NULL, uint32_t length = 0);
+ virtual const CWebSocketFrame* Ping(const char* data = NULL) const = 0;
+ virtual const CWebSocketFrame* Pong(const char* data = NULL) const = 0;
+ virtual const CWebSocketFrame* Close(WebSocketCloseReason reason = WebSocketCloseNormal, const std::string &message = "") = 0;
+ virtual void Fail() = 0;
+
+protected:
+ int m_version;
+ WebSocketState m_state;
+ CWebSocketMessage *m_message;
+
+ virtual CWebSocketFrame* GetFrame(const char* data, uint64_t length) = 0;
+ virtual CWebSocketFrame* GetFrame(WebSocketFrameOpcode opcode, const char* data = NULL, uint32_t length = 0, bool final = true, bool masked = false, int32_t mask = 0, int8_t extension = 0) = 0;
+ virtual CWebSocketMessage* GetMessage() = 0;
+};
diff --git a/xbmc/network/websocket/WebSocketManager.cpp b/xbmc/network/websocket/WebSocketManager.cpp
new file mode 100644
index 0000000..6fef69a
--- /dev/null
+++ b/xbmc/network/websocket/WebSocketManager.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "WebSocketManager.h"
+
+#include "WebSocket.h"
+#include "WebSocketV13.h"
+#include "WebSocketV8.h"
+#include "utils/HttpParser.h"
+#include "utils/HttpResponse.h"
+#include "utils/log.h"
+
+#include <string>
+
+#define WS_HTTP_METHOD "GET"
+#define WS_HTTP_TAG "HTTP/"
+#define WS_SUPPORTED_VERSIONS "8, 13"
+
+#define WS_HEADER_VERSION "Sec-WebSocket-Version"
+#define WS_HEADER_VERSION_LC "sec-websocket-version" // "Sec-WebSocket-Version"
+
+CWebSocket* CWebSocketManager::Handle(const char* data, unsigned int length, std::string &response)
+{
+ if (data == NULL || length <= 0)
+ return NULL;
+
+ HttpParser header;
+ HttpParser::status_t status = header.addBytes(data, length);
+ switch (status)
+ {
+ case HttpParser::Error:
+ case HttpParser::Incomplete:
+ response.clear();
+ return NULL;
+
+ case HttpParser::Done:
+ default:
+ break;
+ }
+
+ // There must be a "Sec-WebSocket-Version" header
+ const char* value = header.getValue(WS_HEADER_VERSION_LC);
+ if (value == NULL)
+ {
+ CLog::Log(LOGINFO, "WebSocket: missing Sec-WebSocket-Version");
+ CHttpResponse httpResponse(HTTP::Get, HTTP::BadRequest, HTTP::Version1_1);
+ response = httpResponse.Create();
+
+ return NULL;
+ }
+
+ CWebSocket *websocket = NULL;
+ if (strncmp(value, "8", 1) == 0)
+ websocket = new CWebSocketV8();
+ else if (strncmp(value, "13", 2) == 0)
+ websocket = new CWebSocketV13();
+
+ if (websocket == NULL)
+ {
+ CLog::Log(LOGINFO, "WebSocket: Unsupported Sec-WebSocket-Version {}", value);
+ CHttpResponse httpResponse(HTTP::Get, HTTP::UpgradeRequired, HTTP::Version1_1);
+ httpResponse.AddHeader(WS_HEADER_VERSION, WS_SUPPORTED_VERSIONS);
+ response = httpResponse.Create();
+
+ return NULL;
+ }
+
+ if (websocket->Handshake(data, length, response))
+ return websocket;
+
+ return NULL;
+}
diff --git a/xbmc/network/websocket/WebSocketManager.h b/xbmc/network/websocket/WebSocketManager.h
new file mode 100644
index 0000000..7cbe8e9
--- /dev/null
+++ b/xbmc/network/websocket/WebSocketManager.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <string>
+
+class CWebSocket;
+
+class CWebSocketManager
+{
+public:
+ static CWebSocket* Handle(const char* data, unsigned int length, std::string &response);
+};
diff --git a/xbmc/network/websocket/WebSocketV13.cpp b/xbmc/network/websocket/WebSocketV13.cpp
new file mode 100644
index 0000000..e0ef8f0
--- /dev/null
+++ b/xbmc/network/websocket/WebSocketV13.cpp
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "WebSocketV13.h"
+
+#include "WebSocket.h"
+#include "utils/HttpParser.h"
+#include "utils/HttpResponse.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <sstream>
+#include <string>
+
+#define WS_HTTP_METHOD "GET"
+#define WS_HTTP_TAG "HTTP/"
+
+#define WS_HEADER_UPGRADE "Upgrade"
+#define WS_HEADER_UPGRADE_LC "upgrade"
+#define WS_HEADER_CONNECTION "Connection"
+#define WS_HEADER_CONNECTION_LC "connection"
+
+#define WS_HEADER_KEY_LC "sec-websocket-key" // "Sec-WebSocket-Key"
+#define WS_HEADER_ACCEPT "Sec-WebSocket-Accept"
+#define WS_HEADER_PROTOCOL "Sec-WebSocket-Protocol"
+#define WS_HEADER_PROTOCOL_LC "sec-websocket-protocol" // "Sec-WebSocket-Protocol"
+
+#define WS_PROTOCOL_JSONRPC "jsonrpc.xbmc.org"
+#define WS_HEADER_UPGRADE_VALUE "websocket"
+
+bool CWebSocketV13::Handshake(const char* data, size_t length, std::string &response)
+{
+ std::string strHeader(data, length);
+ const char *value;
+ HttpParser header;
+ if (header.addBytes(data, length) != HttpParser::Done)
+ {
+ CLog::Log(LOGINFO, "WebSocket [RFC6455]: incomplete handshake received");
+ return false;
+ }
+
+ // The request must be GET
+ value = header.getMethod();
+ if (value == NULL ||
+ StringUtils::CompareNoCase(value, WS_HTTP_METHOD, strlen(WS_HTTP_METHOD)) != 0)
+ {
+ CLog::Log(LOGINFO, "WebSocket [RFC6455]: invalid HTTP method received (GET expected)");
+ return false;
+ }
+
+ // The request must be HTTP/1.1 or higher
+ size_t pos;
+ if ((pos = strHeader.find(WS_HTTP_TAG)) == std::string::npos)
+ {
+ CLog::Log(LOGINFO, "WebSocket [RFC6455]: invalid handshake received");
+ return false;
+ }
+
+ pos += strlen(WS_HTTP_TAG);
+ std::istringstream converter(strHeader.substr(pos, strHeader.find_first_of(" \r\n\t", pos) - pos));
+ float fVersion;
+ converter >> fVersion;
+
+ if (fVersion < 1.1f)
+ {
+ CLog::Log(LOGINFO, "WebSocket [RFC6455]: invalid HTTP version {:f} (1.1 or higher expected)",
+ fVersion);
+ return false;
+ }
+
+ std::string websocketKey, websocketProtocol;
+ // There must be a "Host" header
+ value = header.getValue("host");
+ if (value == NULL || strlen(value) == 0)
+ {
+ CLog::Log(LOGINFO, "WebSocket [RFC6455]: \"Host\" header missing");
+ return true;
+ }
+
+ // There must be a "Upgrade" header with the value "websocket"
+ value = header.getValue(WS_HEADER_UPGRADE_LC);
+ if (value == NULL || StringUtils::CompareNoCase(value, WS_HEADER_UPGRADE_VALUE,
+ strlen(WS_HEADER_UPGRADE_VALUE)) != 0)
+ {
+ CLog::Log(LOGINFO, "WebSocket [RFC6455]: invalid \"{}\" received", WS_HEADER_UPGRADE);
+ return true;
+ }
+
+ // There must be a "Connection" header with the value "Upgrade"
+ value = header.getValue(WS_HEADER_CONNECTION_LC);
+ std::vector<std::string> elements;
+ if (value != nullptr)
+ elements = StringUtils::Split(value, ",");
+ if (elements.empty() || !std::any_of(elements.begin(), elements.end(), [](std::string& elem) { return StringUtils::EqualsNoCase(StringUtils::Trim(elem), WS_HEADER_UPGRADE); }))
+ {
+ CLog::Log(LOGINFO, "WebSocket [RFC6455]: invalid \"{}\" received", WS_HEADER_CONNECTION_LC);
+ return true;
+ }
+
+ // There must be a base64 encoded 16 byte (=> 24 byte as base62) "Sec-WebSocket-Key" header
+ value = header.getValue(WS_HEADER_KEY_LC);
+ if (value == NULL || (websocketKey = value).size() != 24)
+ {
+ CLog::Log(LOGINFO, "WebSocket [RFC6455]: invalid \"Sec-WebSocket-Key\" received");
+ return true;
+ }
+
+ // There might be a "Sec-WebSocket-Protocol" header
+ value = header.getValue(WS_HEADER_PROTOCOL_LC);
+ if (value && strlen(value) > 0)
+ {
+ std::vector<std::string> protocols = StringUtils::Split(value, ",");
+ for (auto& protocol : protocols)
+ {
+ StringUtils::Trim(protocol);
+ if (protocol == WS_PROTOCOL_JSONRPC)
+ {
+ websocketProtocol = WS_PROTOCOL_JSONRPC;
+ break;
+ }
+ }
+ }
+
+ CHttpResponse httpResponse(HTTP::Get, HTTP::SwitchingProtocols, HTTP::Version1_1);
+ httpResponse.AddHeader(WS_HEADER_UPGRADE, WS_HEADER_UPGRADE_VALUE);
+ httpResponse.AddHeader(WS_HEADER_CONNECTION, WS_HEADER_UPGRADE);
+ std::string responseKey = calculateKey(websocketKey);
+ httpResponse.AddHeader(WS_HEADER_ACCEPT, responseKey);
+ if (!websocketProtocol.empty())
+ httpResponse.AddHeader(WS_HEADER_PROTOCOL, websocketProtocol);
+
+ response = httpResponse.Create();
+
+ m_state = WebSocketStateConnected;
+
+ return true;
+}
+
+const CWebSocketFrame* CWebSocketV13::Close(WebSocketCloseReason reason /* = WebSocketCloseNormal */, const std::string &message /* = "" */)
+{
+ if (m_state == WebSocketStateNotConnected || m_state == WebSocketStateHandshaking || m_state == WebSocketStateClosed)
+ {
+ CLog::Log(LOGINFO, "WebSocket [RFC6455]: Cannot send a closing handshake if no connection has been established");
+ return NULL;
+ }
+
+ return close(reason, message);
+}
diff --git a/xbmc/network/websocket/WebSocketV13.h b/xbmc/network/websocket/WebSocketV13.h
new file mode 100644
index 0000000..80c5a73
--- /dev/null
+++ b/xbmc/network/websocket/WebSocketV13.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "WebSocketV8.h"
+
+#include <string>
+
+class CWebSocketV13 : public CWebSocketV8
+{
+public:
+ CWebSocketV13() { m_version = 13; }
+
+ bool Handshake(const char* data, size_t length, std::string &response) override;
+ const CWebSocketFrame* Close(WebSocketCloseReason reason = WebSocketCloseNormal, const std::string &message = "") override;
+};
diff --git a/xbmc/network/websocket/WebSocketV8.cpp b/xbmc/network/websocket/WebSocketV8.cpp
new file mode 100644
index 0000000..e0a187b
--- /dev/null
+++ b/xbmc/network/websocket/WebSocketV8.cpp
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "WebSocketV8.h"
+
+#include "WebSocket.h"
+#include "utils/Base64.h"
+#include "utils/Digest.h"
+#include "utils/EndianSwap.h"
+#include "utils/HttpParser.h"
+#include "utils/HttpResponse.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <sstream>
+#include <string>
+
+using KODI::UTILITY::CDigest;
+
+#define WS_HTTP_METHOD "GET"
+#define WS_HTTP_TAG "HTTP/"
+
+#define WS_HEADER_UPGRADE "Upgrade"
+#define WS_HEADER_CONNECTION "Connection"
+
+#define WS_HEADER_KEY_LC "sec-websocket-key" // "Sec-WebSocket-Key"
+#define WS_HEADER_ACCEPT "Sec-WebSocket-Accept"
+#define WS_HEADER_PROTOCOL "Sec-WebSocket-Protocol"
+#define WS_HEADER_PROTOCOL_LC "sec-websocket-protocol" // "Sec-WebSocket-Protocol"
+
+#define WS_PROTOCOL_JSONRPC "jsonrpc.xbmc.org"
+#define WS_HEADER_UPGRADE_VALUE "websocket"
+#define WS_KEY_MAGICSTRING "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+
+bool CWebSocketV8::Handshake(const char* data, size_t length, std::string &response)
+{
+ std::string strHeader(data, length);
+ const char *value;
+ HttpParser header;
+ if (header.addBytes(data, length) != HttpParser::Done)
+ {
+ CLog::Log(LOGINFO, "WebSocket [hybi-10]: incomplete handshake received");
+ return false;
+ }
+
+ // The request must be GET
+ value = header.getMethod();
+ if (value == NULL ||
+ StringUtils::CompareNoCase(value, WS_HTTP_METHOD, strlen(WS_HTTP_METHOD)) != 0)
+ {
+ CLog::Log(LOGINFO, "WebSocket [hybi-10]: invalid HTTP method received (GET expected)");
+ return false;
+ }
+
+ // The request must be HTTP/1.1 or higher
+ size_t pos;
+ if ((pos = strHeader.find(WS_HTTP_TAG)) == std::string::npos)
+ {
+ CLog::Log(LOGINFO, "WebSocket [hybi-10]: invalid handshake received");
+ return false;
+ }
+
+ pos += strlen(WS_HTTP_TAG);
+ std::istringstream converter(strHeader.substr(pos, strHeader.find_first_of(" \r\n\t", pos) - pos));
+ float fVersion;
+ converter >> fVersion;
+
+ if (fVersion < 1.1f)
+ {
+ CLog::Log(LOGINFO, "WebSocket [hybi-10]: invalid HTTP version {:f} (1.1 or higher expected)",
+ fVersion);
+ return false;
+ }
+
+ std::string websocketKey, websocketProtocol;
+ // There must be a "Host" header
+ value = header.getValue("host");
+ if (value == NULL || strlen(value) == 0)
+ {
+ CLog::Log(LOGINFO, "WebSocket [hybi-10]: \"Host\" header missing");
+ return true;
+ }
+
+ // There must be a base64 encoded 16 byte (=> 24 byte as base64) "Sec-WebSocket-Key" header
+ value = header.getValue(WS_HEADER_KEY_LC);
+ if (value == NULL || (websocketKey = value).size() != 24)
+ {
+ CLog::Log(LOGINFO, "WebSocket [hybi-10]: invalid \"Sec-WebSocket-Key\" received");
+ return true;
+ }
+
+ // There might be a "Sec-WebSocket-Protocol" header
+ value = header.getValue(WS_HEADER_PROTOCOL_LC);
+ if (value && strlen(value) > 0)
+ {
+ std::vector<std::string> protocols = StringUtils::Split(value, ",");
+ for (auto& protocol : protocols)
+ {
+ StringUtils::Trim(protocol);
+ if (protocol == WS_PROTOCOL_JSONRPC)
+ {
+ websocketProtocol = WS_PROTOCOL_JSONRPC;
+ break;
+ }
+ }
+ }
+
+ CHttpResponse httpResponse(HTTP::Get, HTTP::SwitchingProtocols, HTTP::Version1_1);
+ httpResponse.AddHeader(WS_HEADER_UPGRADE, WS_HEADER_UPGRADE_VALUE);
+ httpResponse.AddHeader(WS_HEADER_CONNECTION, WS_HEADER_UPGRADE);
+ httpResponse.AddHeader(WS_HEADER_ACCEPT, calculateKey(websocketKey));
+ if (!websocketProtocol.empty())
+ httpResponse.AddHeader(WS_HEADER_PROTOCOL, websocketProtocol);
+
+ response = httpResponse.Create();
+
+ m_state = WebSocketStateConnected;
+
+ return true;
+}
+
+const CWebSocketFrame* CWebSocketV8::Close(WebSocketCloseReason reason /* = WebSocketCloseNormal */, const std::string &message /* = "" */)
+{
+ if (m_state == WebSocketStateNotConnected || m_state == WebSocketStateHandshaking || m_state == WebSocketStateClosed)
+ {
+ CLog::Log(LOGINFO, "WebSocket [hybi-10]: Cannot send a closing handshake if no connection has been established");
+ return NULL;
+ }
+
+ return close(reason, message);
+}
+
+void CWebSocketV8::Fail()
+{
+ m_state = WebSocketStateClosed;
+}
+
+CWebSocketFrame* CWebSocketV8::GetFrame(const char* data, uint64_t length)
+{
+ return new CWebSocketFrame(data, length);
+}
+
+CWebSocketFrame* CWebSocketV8::GetFrame(WebSocketFrameOpcode opcode, const char* data /* = NULL */, uint32_t length /* = 0 */,
+ bool final /* = true */, bool masked /* = false */, int32_t mask /* = 0 */, int8_t extension /* = 0 */)
+{
+ return new CWebSocketFrame(opcode, data, length, final, masked, mask, extension);
+}
+
+CWebSocketMessage* CWebSocketV8::GetMessage()
+{
+ return new CWebSocketMessage();
+}
+
+const CWebSocketFrame* CWebSocketV8::close(WebSocketCloseReason reason /* = WebSocketCloseNormal */, const std::string &message /* = "" */)
+{
+ size_t length = 2 + message.size();
+
+ char* data = new char[length + 1];
+ memset(data, 0, length + 1);
+ uint16_t iReason = Endian_SwapBE16((uint16_t)reason);
+ memcpy(data, &iReason, 2);
+ message.copy(data + 2, message.size());
+
+ if (m_state == WebSocketStateConnected)
+ m_state = WebSocketStateClosing;
+ else
+ m_state = WebSocketStateClosed;
+
+ CWebSocketFrame* frame = new CWebSocketFrame(WebSocketConnectionClose, data, length);
+ delete[] data;
+
+ return frame;
+}
+
+std::string CWebSocketV8::calculateKey(const std::string &key)
+{
+ std::string acceptKey = key;
+ acceptKey.append(WS_KEY_MAGICSTRING);
+
+ CDigest digest{CDigest::Type::SHA1};
+ digest.Update(acceptKey);
+
+ return Base64::Encode(digest.FinalizeRaw());
+}
diff --git a/xbmc/network/websocket/WebSocketV8.h b/xbmc/network/websocket/WebSocketV8.h
new file mode 100644
index 0000000..d86688c
--- /dev/null
+++ b/xbmc/network/websocket/WebSocketV8.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2011-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "WebSocket.h"
+
+#include <string>
+
+class CWebSocketV8 : public CWebSocket
+{
+public:
+ CWebSocketV8() { m_version = 8; }
+
+ bool Handshake(const char* data, size_t length, std::string &response) override;
+ const CWebSocketFrame* Ping(const char* data = NULL) const override { return new CWebSocketFrame(WebSocketPing, data); }
+ const CWebSocketFrame* Pong(const char* data = NULL) const override { return new CWebSocketFrame(WebSocketPong, data); }
+ const CWebSocketFrame* Close(WebSocketCloseReason reason = WebSocketCloseNormal, const std::string &message = "") override;
+ void Fail() override;
+
+protected:
+ CWebSocketFrame* GetFrame(const char* data, uint64_t length) override;
+ CWebSocketFrame* GetFrame(WebSocketFrameOpcode opcode, const char* data = NULL, uint32_t length = 0, bool final = true, bool masked = false, int32_t mask = 0, int8_t extension = 0) override;
+ CWebSocketMessage* GetMessage() override;
+ virtual const CWebSocketFrame* close(WebSocketCloseReason reason = WebSocketCloseNormal, const std::string &message = "");
+
+ std::string calculateKey(const std::string &key);
+};