2 * Copyright (C) 2011-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
23 #include <boost/uuid/sha1.hpp>
25 #include "WebSocketV8.h"
26 #include "WebSocket.h"
27 #include "utils/Base64.h"
28 #include "utils/EndianSwap.h"
29 #include "utils/HttpParser.h"
30 #include "utils/HttpResponse.h"
31 #include "utils/log.h"
32 #include "utils/StringUtils.h"
34 #define WS_HTTP_METHOD "GET"
35 #define WS_HTTP_TAG "HTTP/"
37 #define WS_HEADER_UPGRADE "Upgrade"
38 #define WS_HEADER_CONNECTION "Connection"
40 #define WS_HEADER_KEY_LC "sec-websocket-key" // "Sec-WebSocket-Key"
41 #define WS_HEADER_ACCEPT "Sec-WebSocket-Accept"
42 #define WS_HEADER_PROTOCOL "Sec-WebSocket-Protocol"
43 #define WS_HEADER_PROTOCOL_LC "sec-websocket-protocol" // "Sec-WebSocket-Protocol"
45 #define WS_PROTOCOL_JSONRPC "jsonrpc.xbmc.org"
46 #define WS_HEADER_UPGRADE_VALUE "websocket"
47 #define WS_KEY_MAGICSTRING "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
51 bool CWebSocketV8::Handshake(const char* data, size_t length, std::string &response)
53 string strHeader(data, length);
56 if (header.addBytes(data, length) != HttpParser::Done)
58 CLog::Log(LOGINFO, "WebSocket [hybi-10]: incomplete handshake received");
62 // The request must be GET
63 value = header.getMethod();
64 if (value == NULL || strnicmp(value, WS_HTTP_METHOD, strlen(WS_HTTP_METHOD)) != 0)
66 CLog::Log(LOGINFO, "WebSocket [hybi-10]: invalid HTTP method received (GET expected)");
70 // The request must be HTTP/1.1 or higher
72 if ((pos = strHeader.find(WS_HTTP_TAG)) == string::npos)
74 CLog::Log(LOGINFO, "WebSocket [hybi-10]: invalid handshake received");
78 pos += strlen(WS_HTTP_TAG);
79 istringstream converter(strHeader.substr(pos, strHeader.find_first_of(" \r\n\t", pos) - pos));
81 converter >> fVersion;
85 CLog::Log(LOGINFO, "WebSocket [hybi-10]: invalid HTTP version %f (1.1 or higher expected)", fVersion);
89 string websocketKey, websocketProtocol;
90 // There must be a "Host" header
91 value = header.getValue("host");
92 if (value == NULL || strlen(value) == 0)
94 CLog::Log(LOGINFO, "WebSocket [hybi-10]: \"Host\" header missing");
98 // There must be a base64 encoded 16 byte (=> 24 byte as base64) "Sec-WebSocket-Key" header
99 value = header.getValue(WS_HEADER_KEY_LC);
100 if (value == NULL || (websocketKey = value).size() != 24)
102 CLog::Log(LOGINFO, "WebSocket [hybi-10]: invalid \"Sec-WebSocket-Key\" received");
106 // There might be a "Sec-WebSocket-Protocol" header
107 value = header.getValue(WS_HEADER_PROTOCOL_LC);
108 if (value && strlen(value) > 0)
110 CStdStringArray protocols;
111 StringUtils::SplitString(value, ",", protocols);
112 for (CStdStringArray::iterator protocol = protocols.begin(); protocol != protocols.end(); ++protocol)
114 StringUtils::Trim(*protocol);
115 if (*protocol == WS_PROTOCOL_JSONRPC)
117 websocketProtocol = WS_PROTOCOL_JSONRPC;
123 CHttpResponse httpResponse(HTTP::Get, HTTP::SwitchingProtocols, HTTP::Version1_1);
124 httpResponse.AddHeader(WS_HEADER_UPGRADE, WS_HEADER_UPGRADE_VALUE);
125 httpResponse.AddHeader(WS_HEADER_CONNECTION, WS_HEADER_UPGRADE);
126 httpResponse.AddHeader(WS_HEADER_ACCEPT, calculateKey(websocketKey));
127 if (!websocketProtocol.empty())
128 httpResponse.AddHeader(WS_HEADER_PROTOCOL, websocketProtocol);
130 char *responseBuffer;
131 int responseLength = httpResponse.Create(responseBuffer);
132 response = std::string(responseBuffer, responseLength);
134 m_state = WebSocketStateConnected;
139 const CWebSocketFrame* CWebSocketV8::Close(WebSocketCloseReason reason /* = WebSocketCloseNormal */, const std::string &message /* = "" */)
141 if (m_state == WebSocketStateNotConnected || m_state == WebSocketStateHandshaking || m_state == WebSocketStateClosed)
143 CLog::Log(LOGINFO, "WebSocket [hybi-10]: Cannot send a closing handshake if no connection has been established");
147 return close(reason, message);
150 void CWebSocketV8::Fail()
152 m_state = WebSocketStateClosed;
155 CWebSocketFrame* CWebSocketV8::GetFrame(const char* data, uint64_t length)
157 return new CWebSocketFrame(data, length);
160 CWebSocketFrame* CWebSocketV8::GetFrame(WebSocketFrameOpcode opcode, const char* data /* = NULL */, uint32_t length /* = 0 */,
161 bool final /* = true */, bool masked /* = false */, int32_t mask /* = 0 */, int8_t extension /* = 0 */)
163 return new CWebSocketFrame(opcode, data, length, final, masked, mask, extension);
166 CWebSocketMessage* CWebSocketV8::GetMessage()
168 return new CWebSocketMessage();
171 const CWebSocketFrame* CWebSocketV8::close(WebSocketCloseReason reason /* = WebSocketCloseNormal */, const std::string &message /* = "" */)
173 size_t length = 2 + message.size();
175 char* data = new char[length + 1];
176 memset(data, 0, length + 1);
177 uint16_t iReason = Endian_SwapBE16((uint16_t)reason);
178 memcpy(data, &iReason, 2);
179 message.copy(data + 2, message.size());
181 if (m_state == WebSocketStateConnected)
182 m_state = WebSocketStateClosing;
184 m_state = WebSocketStateClosed;
186 CWebSocketFrame* frame = new CWebSocketFrame(WebSocketConnectionClose, data, length);
192 std::string CWebSocketV8::calculateKey(const std::string &key)
194 string acceptKey = key;
195 acceptKey.append(WS_KEY_MAGICSTRING);
197 boost::uuids::detail::sha1 hash;
198 hash.process_bytes(acceptKey.c_str(), acceptKey.size());
200 unsigned int digest[5];
201 hash.get_digest(digest);
203 for (unsigned int index = 0; index < 5; index++)
204 digest[index] = Endian_SwapBE32(digest[index]);
206 return Base64::Encode((const char*)digest, sizeof(digest));