diff options
Diffstat (limited to 'xbmc/network/websocket/WebSocketV8.cpp')
-rw-r--r-- | xbmc/network/websocket/WebSocketV8.cpp | 189 |
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()); +} |