#include #include #include #include #include namespace websockets { WebsocketsClient::WebsocketsClient() : WebsocketsClient(std::make_shared()) { // Empty } WebsocketsClient::WebsocketsClient(std::shared_ptr client) : _client(client), _endpoint(client), _connectionOpen(client->available()), _messagesCallback([](WebsocketsClient&, WebsocketsMessage){}), _eventsCallback([](WebsocketsClient&, WebsocketsEvent, WSInterfaceString){}), _sendMode(SendMode_Normal) { // Empty } WebsocketsClient::WebsocketsClient(const WebsocketsClient& other) : _client(other._client), _endpoint(other._endpoint), _connectionOpen(other._client->available()), _messagesCallback(other._messagesCallback), _eventsCallback(other._eventsCallback), _sendMode(other._sendMode) { // delete other's client const_cast(other)._client = nullptr; const_cast(other)._connectionOpen = false; } WebsocketsClient::WebsocketsClient(const WebsocketsClient&& other) : _client(other._client), _endpoint(other._endpoint), _connectionOpen(other._client->available()), _messagesCallback(other._messagesCallback), _eventsCallback(other._eventsCallback), _sendMode(other._sendMode) { // delete other's client const_cast(other)._client = nullptr; const_cast(other)._connectionOpen = false; } WebsocketsClient& WebsocketsClient::operator=(const WebsocketsClient& other) { // call endpoint's copy operator _endpoint = other._endpoint; // get callbacks and data from other this->_client = other._client; this->_messagesCallback = other._messagesCallback; this->_eventsCallback = other._eventsCallback; this->_connectionOpen = other._connectionOpen; this->_sendMode = other._sendMode; // delete other's client const_cast(other)._client = nullptr; const_cast(other)._connectionOpen = false; return *this; } WebsocketsClient& WebsocketsClient::operator=(const WebsocketsClient&& other) { // call endpoint's copy operator _endpoint = other._endpoint; // get callbacks and data from other this->_client = other._client; this->_messagesCallback = other._messagesCallback; this->_eventsCallback = other._eventsCallback; this->_connectionOpen = other._connectionOpen; this->_sendMode = other._sendMode; // delete other's client const_cast(other)._client = nullptr; const_cast(other)._connectionOpen = false; return *this; } struct HandshakeRequestResult { WSString requestStr; WSString expectedAcceptKey; }; bool shouldAddDefaultHeader(const std::string& keyWord, const std::vector>& customHeaders) { for (const auto& header : customHeaders) { if(!keyWord.compare(header.first)) { return false; } } return true; } HandshakeRequestResult generateHandshake(const WSString& host, const WSString& uri, const std::vector>& customHeaders) { WSString key = crypto::base64Encode(crypto::randomBytes(16)); WSString handshake = "GET " + uri + " HTTP/1.1\r\n"; handshake += "Host: " + host + "\r\n"; handshake += "Sec-WebSocket-Key: " + key + "\r\n"; for (const auto& header: customHeaders) { handshake += header.first + ": " + header.second + "\r\n"; } if (shouldAddDefaultHeader("Upgrade", customHeaders)) { handshake += "Upgrade: websocket\r\n"; } if (shouldAddDefaultHeader("Connection", customHeaders)) { handshake += "Connection: Upgrade\r\n"; } if (shouldAddDefaultHeader("Sec-WebSocket-Version", customHeaders)) { handshake += "Sec-WebSocket-Version: 13\r\n"; } if (shouldAddDefaultHeader("User-Agent", customHeaders)) { handshake += "User-Agent: TinyWebsockets Client\r\n"; } if (shouldAddDefaultHeader("Origin", customHeaders)) { handshake += "Origin: https://github.com/gilmaimon/TinyWebsockets\r\n"; } handshake += "\r\n"; HandshakeRequestResult result; result.requestStr = handshake; #ifndef _WS_CONFIG_SKIP_HANDSHAKE_ACCEPT_VALIDATION result.expectedAcceptKey = crypto::websocketsHandshakeEncodeKey(key); #endif return result; } bool isWhitespace(char ch) { return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'; } struct HandshakeResponseResult { bool isSuccess; WSString serverAccept; }; bool isCaseInsensetiveEqual(const WSString lhs, const WSString rhs) { if (lhs.size() != rhs.size()) return false; for (size_t i = 0; i < lhs.size(); i++) { char leftLowerCaseChar = lhs[i] >= 'A' && lhs[i] <= 'Z' ? lhs[i] - 'A' + 'a' : lhs[i]; char righerLowerCaseChar = rhs[i] >= 'A' && rhs[i] <= 'Z' ? rhs[i] - 'A' + 'a' : rhs[i]; if (leftLowerCaseChar != righerLowerCaseChar) return false; } return true; } HandshakeResponseResult parseHandshakeResponse(std::vector responseHeaders) { bool didUpgradeToWebsockets = false, isConnectionUpgraded = false; WSString serverAccept = ""; for(WSString header : responseHeaders) { auto colonIndex = header.find_first_of(':'); WSString key = header.substr(0, colonIndex); WSString value = header.substr(colonIndex + 2); // +2 (ignore space and ':') if(isCaseInsensetiveEqual(key, "upgrade")) { didUpgradeToWebsockets = isCaseInsensetiveEqual(value, "Websocket"); } else if(isCaseInsensetiveEqual(key, "connection")) { isConnectionUpgraded = isCaseInsensetiveEqual(value, "upgrade"); } else if(isCaseInsensetiveEqual(key, "Sec-WebSocket-Accept")) { serverAccept = value; } } HandshakeResponseResult result; result.isSuccess = serverAccept != "" && didUpgradeToWebsockets && isConnectionUpgraded; result.serverAccept = serverAccept; return result; } bool doestStartsWith(WSString str, WSString prefix) { if(str.size() < prefix.size()) return false; for(size_t i = 0; i < prefix.size(); i++) { if(str[i] != prefix[i]) return false; } return true; } void WebsocketsClient::upgradeToSecuredConnection() { #ifndef _WS_CONFIG_NO_SSL auto client = new WSDefaultSecuredTcpClient; #ifdef ESP8266 if( this->_optional_ssl_fingerprint || (this->_optional_ssl_rsa_cert && this->_optional_ssl_rsa_private_key) || (this->_optional_ssl_ec_cert && this->_optional_ssl_ec_private_key) || this->_optional_ssl_trust_anchors || this->_optional_ssl_known_key ) { if(this->_optional_ssl_fingerprint) { client->setFingerprint(this->_optional_ssl_fingerprint); } if(this->_optional_ssl_trust_anchors) { client->setTrustAnchors(this->_optional_ssl_trust_anchors); } if(this->_optional_ssl_known_key) { client->setKnownKey(this->_optional_ssl_known_key); } if(this->_optional_ssl_rsa_cert && this->_optional_ssl_rsa_private_key) { client->setClientRSACert(this->_optional_ssl_rsa_cert, this->_optional_ssl_rsa_private_key); } if(this->_optional_ssl_ec_cert && this->_optional_ssl_ec_private_key) { client->setClientECCert(this->_optional_ssl_ec_cert, this->_optional_ssl_ec_private_key); } } else { client->setInsecure(); } #elif defined(ESP32) if(this->_optional_ssl_ca_cert) { client->setCACert(this->_optional_ssl_ca_cert); } if(this->_optional_ssl_client_ca) { client->setCertificate(this->_optional_ssl_client_ca); } if(this->_optional_ssl_private_key) { client->setPrivateKey(this->_optional_ssl_private_key); } #endif this->_client = std::shared_ptr(client); this->_endpoint.setInternalSocket(this->_client); #endif //_WS_CONFIG_NO_SSL } void WebsocketsClient::addHeader(const WSInterfaceString key, const WSInterfaceString value) { _customHeaders.push_back({internals::fromInterfaceString(key), internals::fromInterfaceString(value)}); } bool WebsocketsClient::connect(WSInterfaceString _url) { WSString url = internals::fromInterfaceString(_url); WSString protocol = ""; int defaultPort = 0; if(doestStartsWith(url, "http://")) { defaultPort = 80; protocol = "http"; url = url.substr(7); //strlen("http://") == 7 } else if(doestStartsWith(url, "ws://")) { defaultPort = 80; protocol = "ws"; url = url.substr(5); //strlen("ws://") == 5 } #ifndef _WS_CONFIG_NO_SSL else if(doestStartsWith(url, "wss://")) { defaultPort = 443; protocol = "wss"; url = url.substr(6); //strlen("wss://") == 6 upgradeToSecuredConnection(); } else if(doestStartsWith(url, "https://")) { defaultPort = 443; protocol = "https"; url = url.substr(8); //strlen("https://") == 8 upgradeToSecuredConnection(); } #endif else { return false; // Not supported } auto uriBeg = url.find_first_of('/'); std::string host = url, uri = "/"; if(static_cast(uriBeg) != -1) { uri = url.substr(uriBeg); host = url.substr(0, uriBeg); } auto portIdx = host.find_first_of(':'); int port = defaultPort; if(static_cast(portIdx) != -1) { auto onlyHost = host.substr(0, portIdx); ++portIdx; port = 0; while(portIdx < host.size() && host[portIdx] >= '0' && host[portIdx] <= '9') { port = port * 10 + (host[portIdx] - '0'); ++portIdx; } host = onlyHost; } return this->connect( internals::fromInternalString(host), port, internals::fromInternalString(uri) ); } bool WebsocketsClient::connect(WSInterfaceString host, int port, WSInterfaceString path) { this->_connectionOpen = this->_client->connect(internals::fromInterfaceString(host), port); if (!this->_connectionOpen) return false; auto handshake = generateHandshake(internals::fromInterfaceString(host), internals::fromInterfaceString(path), _customHeaders); this->_client->send(handshake.requestStr); // This check is needed because of an ESP32 lib bug that wont signal that the connection had // failed in `->connect` (called above), sometimes the disconnect will only be noticed here (after a `send`) if(!available()) { return false; } auto head = this->_client->readLine(); if(!doestStartsWith(head, "HTTP/1.1 101")) { close(CloseReason_ProtocolError); return false; } std::vector serverResponseHeaders; WSString line = ""; while (true) { line = this->_client->readLine(); if (line.size() < 2) { close(CloseReason_ProtocolError); return false; } if (line == "\r\n") break; // remove /r/n from line end line = line.substr(0, line.size() - 2); serverResponseHeaders.push_back(line); } auto parsedResponse = parseHandshakeResponse(serverResponseHeaders); #ifdef _WS_CONFIG_SKIP_HANDSHAKE_ACCEPT_VALIDATION bool serverAcceptMismatch = false; #else bool serverAcceptMismatch = parsedResponse.serverAccept != handshake.expectedAcceptKey; #endif if(parsedResponse.isSuccess == false || serverAcceptMismatch) { close(CloseReason_ProtocolError); return false; } this->_eventsCallback(*this, WebsocketsEvent::ConnectionOpened, {}); return true; } bool WebsocketsClient::connectSecure(WSInterfaceString host, int port, WSInterfaceString path) { upgradeToSecuredConnection(); return connect(host, port, path); } void WebsocketsClient::onMessage(MessageCallback callback) { this->_messagesCallback = callback; } void WebsocketsClient::onMessage(PartialMessageCallback callback) { this->_messagesCallback = [callback](WebsocketsClient&, WebsocketsMessage msg) { callback(msg); }; } void WebsocketsClient::onEvent(EventCallback callback) { this->_eventsCallback = callback; } void WebsocketsClient::onEvent(PartialEventCallback callback) { this->_eventsCallback = [callback](WebsocketsClient&, WebsocketsEvent event, WSInterfaceString data) { callback(event, data); }; } bool WebsocketsClient::poll() { bool messageReceived = false; while(available() && _endpoint.poll()) { auto msg = _endpoint.recv(); if(msg.isEmpty()) { continue; } messageReceived = true; if(msg.isBinary() || msg.isText()) { this->_messagesCallback(*this, std::move(msg)); } else if(msg.isContinuation()) { // continuation messages will only be returned when policy is appropriate this->_messagesCallback(*this, std::move(msg)); } else if(msg.isPing()) { _handlePing(std::move(msg)); } else if(msg.isPong()) { _handlePong(std::move(msg)); } else if(msg.isClose()) { this->_connectionOpen = false; _handleClose(std::move(msg)); } } return messageReceived; } WebsocketsMessage WebsocketsClient::readBlocking() { while(available()) { #ifdef PLATFORM_DOES_NOT_SUPPORT_BLOCKING_READ while(available() && _endpoint.poll() == false) continue; #endif auto msg = _endpoint.recv(); if(!msg.isEmpty()) return msg; } return {}; } bool WebsocketsClient::send(const WSInterfaceString& data) { auto str = internals::fromInterfaceString(data); return this->send(str.c_str(), str.size()); } bool WebsocketsClient::send(const WSInterfaceString&& data) { auto str = internals::fromInterfaceString(data); return this->send(str.c_str(), str.size()); } bool WebsocketsClient::send(const char* data) { return this->send(data, strlen(data)); } bool WebsocketsClient::send(const char* data, const size_t len) { if(available()) { // if in normal mode if(this->_sendMode == SendMode_Normal) { // send a normal message return _endpoint.send( data, len, internals::ContentType::Text, true ); } // if in streaming mode else if(this->_sendMode == SendMode_Streaming) { // send a continue frame return _endpoint.send( data, len, internals::ContentType::Continuation, false ); } } return false; } bool WebsocketsClient::sendBinary(WSInterfaceString data) { auto str = internals::fromInterfaceString(data); return this->sendBinary(str.c_str(), str.size()); } bool WebsocketsClient::sendBinary(const char* data, const size_t len) { if(available()) { // if in normal mode if(this->_sendMode == SendMode_Normal) { // send a normal message return _endpoint.send( data, len, internals::ContentType::Binary, true ); } // if in streaming mode else if(this->_sendMode == SendMode_Streaming) { // send a continue frame return _endpoint.send( data, len, internals::ContentType::Continuation, false ); } } return false; } bool WebsocketsClient::stream(const WSInterfaceString data) { if(available() && this->_sendMode == SendMode_Normal) { this->_sendMode = SendMode_Streaming; return _endpoint.send( internals::fromInterfaceString(data), internals::ContentType::Text, false ); } return false; } bool WebsocketsClient::streamBinary(const WSInterfaceString data) { if(available() && this->_sendMode == SendMode_Normal) { this->_sendMode = SendMode_Streaming; return _endpoint.send( internals::fromInterfaceString(data), internals::ContentType::Binary, false ); } return false; } bool WebsocketsClient::end(const WSInterfaceString data) { if(available() && this->_sendMode == SendMode_Streaming) { this->_sendMode = SendMode_Normal; return _endpoint.send( internals::fromInterfaceString(data), internals::ContentType::Continuation, true ); } return false; } void WebsocketsClient::setFragmentsPolicy(const FragmentsPolicy newPolicy) { _endpoint.setFragmentsPolicy(newPolicy); } bool WebsocketsClient::available(const bool activeTest) { if(activeTest) { _endpoint.ping(""); } bool updatedConnectionOpen = this->_connectionOpen && this->_client && this->_client->available(); if(updatedConnectionOpen != this->_connectionOpen) { _endpoint.close(CloseReason_AbnormalClosure); this->_eventsCallback(*this, WebsocketsEvent::ConnectionClosed, ""); } this->_connectionOpen = updatedConnectionOpen; return this->_connectionOpen; } bool WebsocketsClient::ping(const WSInterfaceString data) { if(available()) { return _endpoint.ping(internals::fromInterfaceString(data)); } return false; } bool WebsocketsClient::pong(const WSInterfaceString data) { if(available()) { return _endpoint.pong(internals::fromInterfaceString(data)); } return false; } void WebsocketsClient::close(const CloseReason reason) { if(available()) { this->_connectionOpen = false; _endpoint.close(reason); _handleClose({}); } } CloseReason WebsocketsClient::getCloseReason() const { return _endpoint.getCloseReason(); } void WebsocketsClient::_handlePing(const WebsocketsMessage message) { this->_eventsCallback(*this, WebsocketsEvent::GotPing, message.data()); } void WebsocketsClient::_handlePong(const WebsocketsMessage message) { this->_eventsCallback(*this, WebsocketsEvent::GotPong, message.data()); } void WebsocketsClient::_handleClose(const WebsocketsMessage message) { this->_eventsCallback(*this, WebsocketsEvent::ConnectionClosed, message.data()); } #ifdef ESP8266 void WebsocketsClient::setFingerprint(const char* fingerprint) { this->_optional_ssl_fingerprint = fingerprint; } void WebsocketsClient::setInsecure() { this->_optional_ssl_fingerprint = nullptr; this->_optional_ssl_rsa_cert = nullptr; this->_optional_ssl_rsa_private_key = nullptr; this->_optional_ssl_ec_cert = nullptr; this->_optional_ssl_ec_private_key = nullptr; this->_optional_ssl_trust_anchors = nullptr; this->_optional_ssl_known_key = nullptr; } void WebsocketsClient::setClientRSACert(const X509List *cert, const PrivateKey *sk) { this->_optional_ssl_rsa_cert = cert; this->_optional_ssl_rsa_private_key = sk; } void WebsocketsClient::setClientECCert(const X509List *cert, const PrivateKey *sk) { this->_optional_ssl_ec_cert = cert; this->_optional_ssl_ec_private_key = sk; } void WebsocketsClient::setTrustAnchors(const X509List *ta){ this->_optional_ssl_trust_anchors = ta; } void WebsocketsClient::setKnownKey(const PublicKey *pk) { this->_optional_ssl_known_key = pk; } #elif defined(ESP32) void WebsocketsClient::setCACert(const char* ca_cert) { this->_optional_ssl_ca_cert = ca_cert; } void WebsocketsClient::setCertificate(const char* client_ca) { this->_optional_ssl_client_ca = client_ca; } void WebsocketsClient::setPrivateKey(const char* private_key) { this->_optional_ssl_private_key = private_key; } void WebsocketsClient::setInsecure() { this->_optional_ssl_ca_cert = nullptr; this->_optional_ssl_client_ca = nullptr; this->_optional_ssl_private_key = nullptr; } #endif WebsocketsClient::~WebsocketsClient() { if(available()) { this->close(CloseReason_GoingAway); } } }