/* * 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 #include 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 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()); }