[cstdstring] removal of Trim/TrimLeft/TrimRight
[vuplus_xbmc] / xbmc / network / websocket / WebSocketV8.cpp
1 /*
2  *      Copyright (C) 2011-2013 Team XBMC
3  *      http://xbmc.org
4  *
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)
8  *  any later version.
9  *
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.
14  *
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/>.
18  *
19  */
20
21 #include <string>
22 #include <sstream>
23 #include <boost/uuid/sha1.hpp>
24
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"
33
34 #define WS_HTTP_METHOD          "GET"
35 #define WS_HTTP_TAG             "HTTP/"
36
37 #define WS_HEADER_UPGRADE       "Upgrade"
38 #define WS_HEADER_CONNECTION    "Connection"
39
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"
44
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"
48
49 using namespace std;
50
51 bool CWebSocketV8::Handshake(const char* data, size_t length, std::string &response)
52 {
53   string strHeader(data, length);
54   const char *value;
55   HttpParser header;
56   if (header.addBytes(data, length) != HttpParser::Done)
57   {
58     CLog::Log(LOGINFO, "WebSocket [hybi-10]: incomplete handshake received");
59     return false;
60   }
61
62   // The request must be GET
63   value = header.getMethod();
64   if (value == NULL || strnicmp(value, WS_HTTP_METHOD, strlen(WS_HTTP_METHOD)) != 0)
65   {
66     CLog::Log(LOGINFO, "WebSocket [hybi-10]: invalid HTTP method received (GET expected)");
67     return false;
68   }
69
70   // The request must be HTTP/1.1 or higher
71   size_t pos;
72   if ((pos = strHeader.find(WS_HTTP_TAG)) == string::npos)
73   {
74     CLog::Log(LOGINFO, "WebSocket [hybi-10]: invalid handshake received");
75     return false;
76   }
77
78   pos += strlen(WS_HTTP_TAG);
79   istringstream converter(strHeader.substr(pos, strHeader.find_first_of(" \r\n\t", pos) - pos));
80   float fVersion;
81   converter >> fVersion;
82
83   if (fVersion < 1.1f)
84   {
85     CLog::Log(LOGINFO, "WebSocket [hybi-10]: invalid HTTP version %f (1.1 or higher expected)", fVersion);
86     return false;
87   }
88
89   string websocketKey, websocketProtocol;
90   // There must be a "Host" header
91   value = header.getValue("host");
92   if (value == NULL || strlen(value) == 0)
93   {
94     CLog::Log(LOGINFO, "WebSocket [hybi-10]: \"Host\" header missing");
95     return true;
96   }
97
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)
101   {
102     CLog::Log(LOGINFO, "WebSocket [hybi-10]: invalid \"Sec-WebSocket-Key\" received");
103     return true;
104   }
105
106   // There might be a "Sec-WebSocket-Protocol" header
107   value = header.getValue(WS_HEADER_PROTOCOL_LC);
108   if (value && strlen(value) > 0)
109   {
110     CStdStringArray protocols;
111     StringUtils::SplitString(value, ",", protocols);
112     for (CStdStringArray::iterator protocol = protocols.begin(); protocol != protocols.end(); ++protocol)
113     {
114       StringUtils::Trim(*protocol);
115       if (*protocol == WS_PROTOCOL_JSONRPC)
116       {
117         websocketProtocol = WS_PROTOCOL_JSONRPC;
118         break;
119       }
120     }
121   }
122
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);
129
130   char *responseBuffer;
131   int responseLength = httpResponse.Create(responseBuffer);
132   response = std::string(responseBuffer, responseLength);
133   
134   m_state = WebSocketStateConnected;
135
136   return true;
137 }
138
139 const CWebSocketFrame* CWebSocketV8::Close(WebSocketCloseReason reason /* = WebSocketCloseNormal */, const std::string &message /* = "" */)
140 {
141   if (m_state == WebSocketStateNotConnected || m_state == WebSocketStateHandshaking || m_state == WebSocketStateClosed)
142   {
143     CLog::Log(LOGINFO, "WebSocket [hybi-10]: Cannot send a closing handshake if no connection has been established");
144     return NULL;
145   }
146
147   return close(reason, message);
148 }
149
150 void CWebSocketV8::Fail()
151 {
152   m_state = WebSocketStateClosed;
153 }
154
155 CWebSocketFrame* CWebSocketV8::GetFrame(const char* data, uint64_t length)
156 {
157   return new CWebSocketFrame(data, length);
158 }
159
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 */)
162 {
163   return new CWebSocketFrame(opcode, data, length, final, masked, mask, extension);
164 }
165
166 CWebSocketMessage* CWebSocketV8::GetMessage()
167 {
168   return new CWebSocketMessage();
169 }
170
171 const CWebSocketFrame* CWebSocketV8::close(WebSocketCloseReason reason /* = WebSocketCloseNormal */, const std::string &message /* = "" */)
172 {
173   size_t length = 2 + message.size();
174
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());
180
181   if (m_state == WebSocketStateConnected)
182     m_state = WebSocketStateClosing;
183   else
184     m_state = WebSocketStateClosed;
185
186   CWebSocketFrame* frame = new CWebSocketFrame(WebSocketConnectionClose, data, length);
187   delete[] data;
188
189   return frame;
190 }
191
192 std::string CWebSocketV8::calculateKey(const std::string &key)
193 {
194   string acceptKey = key;
195   acceptKey.append(WS_KEY_MAGICSTRING);
196
197   boost::uuids::detail::sha1 hash;
198   hash.process_bytes(acceptKey.c_str(), acceptKey.size());
199
200   unsigned int digest[5];
201   hash.get_digest(digest);
202
203   for (unsigned int index = 0; index < 5; index++)
204     digest[index] = Endian_SwapBE32(digest[index]);
205
206   return Base64::Encode((const char*)digest, sizeof(digest));
207 }