CWebSocket* CWebSocketManager::Handle(const char* data, unsigned int length, std::string &response) { if (data == NULL || length <= 0) return NULL; HttpParser header; HttpParser::status_t status = header.addBytes(data, length); switch (status) { case HttpParser::Error: case HttpParser::Incomplete: response.clear(); return NULL; case HttpParser::Done: default: break; } // There must be a "Sec-WebSocket-Version" header const char* value = header.getValue(WS_HEADER_VERSION_LC); if (value == NULL) { CLog::Log(LOGINFO, "WebSocket: missing Sec-WebSocket-Version"); CHttpResponse httpResponse(HTTP::Get, HTTP::BadRequest, HTTP::Version1_1); char *responseBuffer; int responseLength = httpResponse.Create(responseBuffer); response = std::string(responseBuffer, responseLength); return NULL; } CWebSocket *websocket = NULL; if (strncmp(value, "8", 1) == 0) websocket = new CWebSocketV8(); else if (strncmp(value, "13", 2) == 0) websocket = new CWebSocketV13(); if (websocket == NULL) { CLog::Log(LOGINFO, "WebSocket: Unsupported Sec-WebSocket-Version %s", value); CHttpResponse httpResponse(HTTP::Get, HTTP::UpgradeRequired, HTTP::Version1_1); httpResponse.AddHeader(WS_HEADER_VERSION, WS_SUPPORTED_VERSIONS); char *responseBuffer; int responseLength = httpResponse.Create(responseBuffer); response = std::string(responseBuffer, responseLength); return NULL; } if (websocket->Handshake(data, length, response)) return websocket; return NULL; }
TEST(TestHttpParser, General) { HttpParser a; std::string str = "POST /path/script.cgi HTTP/1.0\r\n" "From: [email protected]\r\n" "User-Agent: XBMC/snapshot (compatible; MSIE 5.5; Windows NT" " 4.0)\r\n" "Content-Type: application/x-www-form-urlencoded\r\n" "Content-Length: 35\r\n" "\r\n" "home=amejia&favorite+flavor=orange\r\n"; std::string refstr, varstr; EXPECT_EQ(a.Done, a.addBytes(str.c_str(), str.length())); refstr = "POST"; varstr = a.getMethod(); EXPECT_STREQ(refstr.c_str(), varstr.c_str()); refstr = "/path/script.cgi"; varstr = a.getUri(); EXPECT_STREQ(refstr.c_str(), varstr.c_str()); refstr = ""; varstr = a.getQueryString(); EXPECT_STREQ(refstr.c_str(), varstr.c_str()); refstr = "home=amejia&favorite+flavor=orange\r\n"; varstr = a.getBody(); EXPECT_STREQ(refstr.c_str(), varstr.c_str()); refstr = "application/x-www-form-urlencoded"; varstr = a.getValue("content-type"); EXPECT_STREQ(refstr.c_str(), varstr.c_str()); EXPECT_EQ((unsigned)35, a.getContentLength()); }
bool CWebSocketV8::Handshake(const char* data, size_t length, std::string &response) { 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 || strnicmp(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 int pos; if ((pos = strHeader.find(WS_HTTP_TAG)) == string::npos) { CLog::Log(LOGINFO, "WebSocket [hybi-10]: invalid handshake received"); return false; } pos += strlen(WS_HTTP_TAG); 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; } 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) { CStdStringArray protocols; StringUtils::SplitString(value, ",", protocols); for (unsigned int index = 0; index < protocols.size(); index++) { if (protocols.at(index).Trim().Equals(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); char *responseBuffer; int responseLength = httpResponse.Create(responseBuffer); response = std::string(responseBuffer, responseLength); m_state = WebSocketStateConnected; return true; }