summaryrefslogtreecommitdiffstats
path: root/xbmc/network/websocket/WebSocketV8.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/network/websocket/WebSocketV8.cpp')
-rw-r--r--xbmc/network/websocket/WebSocketV8.cpp189
1 files changed, 189 insertions, 0 deletions
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());
+}