104e438cbSEd Tanous #pragma once 23ccb3adbSEd Tanous #include "async_resp.hpp" 304e438cbSEd Tanous #include "http_request.hpp" 404e438cbSEd Tanous 504e438cbSEd Tanous #include <boost/asio/buffer.hpp> 6863c1c2eSEd Tanous #include <boost/beast/core/multi_buffer.hpp> 704e438cbSEd Tanous #include <boost/beast/websocket.hpp> 804e438cbSEd Tanous 904e438cbSEd Tanous #include <array> 1004e438cbSEd Tanous #include <functional> 1104e438cbSEd Tanous 1204e438cbSEd Tanous #ifdef BMCWEB_ENABLE_SSL 1304e438cbSEd Tanous #include <boost/beast/websocket/ssl.hpp> 1404e438cbSEd Tanous #endif 1504e438cbSEd Tanous 1604e438cbSEd Tanous namespace crow 1704e438cbSEd Tanous { 1804e438cbSEd Tanous namespace websocket 1904e438cbSEd Tanous { 2004e438cbSEd Tanous 21863c1c2eSEd Tanous enum class MessageType 22863c1c2eSEd Tanous { 23863c1c2eSEd Tanous Binary, 24863c1c2eSEd Tanous Text, 25863c1c2eSEd Tanous }; 26863c1c2eSEd Tanous 2704e438cbSEd Tanous struct Connection : std::enable_shared_from_this<Connection> 2804e438cbSEd Tanous { 2904e438cbSEd Tanous public: 305ebb9d33SEd Tanous Connection() = default; 3104e438cbSEd Tanous 32ecd6a3a2SEd Tanous Connection(const Connection&) = delete; 33ecd6a3a2SEd Tanous Connection(Connection&&) = delete; 34ecd6a3a2SEd Tanous Connection& operator=(const Connection&) = delete; 35ecd6a3a2SEd Tanous Connection& operator=(const Connection&&) = delete; 36ecd6a3a2SEd Tanous 379eb808c1SEd Tanous virtual void sendBinary(std::string_view msg) = 0; 3804e438cbSEd Tanous virtual void sendBinary(std::string&& msg) = 0; 39863c1c2eSEd Tanous virtual void sendEx(MessageType type, std::string_view msg, 40863c1c2eSEd Tanous std::function<void()>&& onDone) = 0; 419eb808c1SEd Tanous virtual void sendText(std::string_view msg) = 0; 4204e438cbSEd Tanous virtual void sendText(std::string&& msg) = 0; 439eb808c1SEd Tanous virtual void close(std::string_view msg = "quit") = 0; 44863c1c2eSEd Tanous virtual void deferRead() = 0; 45863c1c2eSEd Tanous virtual void resumeRead() = 0; 4604e438cbSEd Tanous virtual boost::asio::io_context& getIoContext() = 0; 4704e438cbSEd Tanous virtual ~Connection() = default; 48052bcbf4SNinad Palsule virtual boost::urls::url_view url() = 0; 4904e438cbSEd Tanous }; 5004e438cbSEd Tanous 5104e438cbSEd Tanous template <typename Adaptor> 5204e438cbSEd Tanous class ConnectionImpl : public Connection 5304e438cbSEd Tanous { 545ebb9d33SEd Tanous using self_t = ConnectionImpl<Adaptor>; 555ebb9d33SEd Tanous 5604e438cbSEd Tanous public: 5704e438cbSEd Tanous ConnectionImpl( 585ebb9d33SEd Tanous const boost::urls::url_view& urlViewIn, 595ebb9d33SEd Tanous const std::shared_ptr<persistent_data::UserSession>& sessionIn, 60052bcbf4SNinad Palsule Adaptor adaptorIn, std::function<void(Connection&)> openHandlerIn, 6104e438cbSEd Tanous std::function<void(Connection&, const std::string&, bool)> 628a592810SEd Tanous messageHandlerIn, 63863c1c2eSEd Tanous std::function<void(crow::websocket::Connection&, std::string_view, 64863c1c2eSEd Tanous crow::websocket::MessageType type, 65863c1c2eSEd Tanous std::function<void()>&& whenComplete)> 66863c1c2eSEd Tanous messageExHandlerIn, 678a592810SEd Tanous std::function<void(Connection&, const std::string&)> closeHandlerIn, 688a592810SEd Tanous std::function<void(Connection&)> errorHandlerIn) : 695ebb9d33SEd Tanous uri(urlViewIn), 705ebb9d33SEd Tanous ws(std::move(adaptorIn)), inBuffer(inString, 131088), 718a592810SEd Tanous openHandler(std::move(openHandlerIn)), 728a592810SEd Tanous messageHandler(std::move(messageHandlerIn)), 73863c1c2eSEd Tanous messageExHandler(std::move(messageExHandlerIn)), 748a592810SEd Tanous closeHandler(std::move(closeHandlerIn)), 755ebb9d33SEd Tanous errorHandler(std::move(errorHandlerIn)), session(sessionIn) 7604e438cbSEd Tanous { 7702bdd967Sdhineskumare /* Turn on the timeouts on websocket stream to server role */ 7802bdd967Sdhineskumare ws.set_option(boost::beast::websocket::stream_base::timeout::suggested( 7902bdd967Sdhineskumare boost::beast::role_type::server)); 8062598e31SEd Tanous BMCWEB_LOG_DEBUG("Creating new connection {}", logPtr(this)); 8104e438cbSEd Tanous } 8204e438cbSEd Tanous 8304e438cbSEd Tanous boost::asio::io_context& getIoContext() override 8404e438cbSEd Tanous { 8504e438cbSEd Tanous return static_cast<boost::asio::io_context&>( 8604e438cbSEd Tanous ws.get_executor().context()); 8704e438cbSEd Tanous } 8804e438cbSEd Tanous 895ebb9d33SEd Tanous void start(const crow::Request& req) 9004e438cbSEd Tanous { 9162598e31SEd Tanous BMCWEB_LOG_DEBUG("starting connection {}", logPtr(this)); 9204e438cbSEd Tanous 9304e438cbSEd Tanous using bf = boost::beast::http::field; 945ebb9d33SEd Tanous std::string protocolHeader = req.req[bf::sec_websocket_protocol]; 9504e438cbSEd Tanous 9604e438cbSEd Tanous ws.set_option(boost::beast::websocket::stream_base::decorator( 975ebb9d33SEd Tanous [session{session}, 985ebb9d33SEd Tanous protocolHeader](boost::beast::websocket::response_type& m) { 9904e438cbSEd Tanous 10004e438cbSEd Tanous #ifndef BMCWEB_INSECURE_DISABLE_CSRF_PREVENTION 101a90daf18SEd Tanous if (session != nullptr) 102a90daf18SEd Tanous { 10304e438cbSEd Tanous // use protocol for csrf checking 1047e9c08edSEd Tanous if (session->cookieAuth && 1057e9c08edSEd Tanous !crow::utility::constantTimeStringCompare( 1065ebb9d33SEd Tanous protocolHeader, session->csrfToken)) 10704e438cbSEd Tanous { 10862598e31SEd Tanous BMCWEB_LOG_ERROR("Websocket CSRF error"); 10904e438cbSEd Tanous m.result(boost::beast::http::status::unauthorized); 11004e438cbSEd Tanous return; 11104e438cbSEd Tanous } 112a90daf18SEd Tanous } 11304e438cbSEd Tanous #endif 1145ebb9d33SEd Tanous if (!protocolHeader.empty()) 11504e438cbSEd Tanous { 1165ebb9d33SEd Tanous m.insert(bf::sec_websocket_protocol, protocolHeader); 11704e438cbSEd Tanous } 11804e438cbSEd Tanous 11904e438cbSEd Tanous m.insert(bf::strict_transport_security, "max-age=31536000; " 12004e438cbSEd Tanous "includeSubdomains; " 12104e438cbSEd Tanous "preload"); 12204e438cbSEd Tanous m.insert(bf::pragma, "no-cache"); 12304e438cbSEd Tanous m.insert(bf::cache_control, "no-Store,no-Cache"); 12404e438cbSEd Tanous m.insert("Content-Security-Policy", "default-src 'self'"); 12504e438cbSEd Tanous m.insert("X-XSS-Protection", "1; " 12604e438cbSEd Tanous "mode=block"); 12704e438cbSEd Tanous m.insert("X-Content-Type-Options", "nosniff"); 12804e438cbSEd Tanous })); 12904e438cbSEd Tanous 1305ebb9d33SEd Tanous // Make a pointer to keep the req alive while we accept it. 1315ebb9d33SEd Tanous using Body = 1325ebb9d33SEd Tanous boost::beast::http::request<boost::beast::http::string_body>; 1335ebb9d33SEd Tanous std::unique_ptr<Body> mobile = std::make_unique<Body>(req.req); 1345ebb9d33SEd Tanous Body* ptr = mobile.get(); 13504e438cbSEd Tanous // Perform the websocket upgrade 1365ebb9d33SEd Tanous ws.async_accept(*ptr, 1375ebb9d33SEd Tanous std::bind_front(&self_t::acceptDone, this, 1385ebb9d33SEd Tanous shared_from_this(), std::move(mobile))); 13904e438cbSEd Tanous } 14004e438cbSEd Tanous 14126ccae32SEd Tanous void sendBinary(std::string_view msg) override 14204e438cbSEd Tanous { 14304e438cbSEd Tanous ws.binary(true); 144863c1c2eSEd Tanous outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()), 145863c1c2eSEd Tanous boost::asio::buffer(msg))); 14604e438cbSEd Tanous doWrite(); 14704e438cbSEd Tanous } 14804e438cbSEd Tanous 149863c1c2eSEd Tanous void sendEx(MessageType type, std::string_view msg, 150863c1c2eSEd Tanous std::function<void()>&& onDone) override 151863c1c2eSEd Tanous { 152863c1c2eSEd Tanous if (doingWrite) 153863c1c2eSEd Tanous { 15462598e31SEd Tanous BMCWEB_LOG_CRITICAL( 15562598e31SEd Tanous "Cannot mix sendEx usage with sendBinary or sendText"); 156863c1c2eSEd Tanous onDone(); 157863c1c2eSEd Tanous return; 158863c1c2eSEd Tanous } 159863c1c2eSEd Tanous ws.binary(type == MessageType::Binary); 160863c1c2eSEd Tanous 161863c1c2eSEd Tanous ws.async_write(boost::asio::buffer(msg), 162863c1c2eSEd Tanous [weak(weak_from_this()), onDone{std::move(onDone)}]( 163863c1c2eSEd Tanous const boost::beast::error_code& ec, size_t) { 164863c1c2eSEd Tanous std::shared_ptr<Connection> self = weak.lock(); 165*a8894201Szhaogang.0108 if (!self) 166*a8894201Szhaogang.0108 { 167*a8894201Szhaogang.0108 BMCWEB_LOG_ERROR("Connection went away"); 168*a8894201Szhaogang.0108 return; 169*a8894201Szhaogang.0108 } 170863c1c2eSEd Tanous 171863c1c2eSEd Tanous // Call the done handler regardless of whether we 172863c1c2eSEd Tanous // errored, but before we close things out 173863c1c2eSEd Tanous onDone(); 174863c1c2eSEd Tanous 175863c1c2eSEd Tanous if (ec) 176863c1c2eSEd Tanous { 17762598e31SEd Tanous BMCWEB_LOG_ERROR("Error in ws.async_write {}", ec); 178863c1c2eSEd Tanous self->close("write error"); 179863c1c2eSEd Tanous } 180863c1c2eSEd Tanous }); 181863c1c2eSEd Tanous } 182863c1c2eSEd Tanous 18304e438cbSEd Tanous void sendBinary(std::string&& msg) override 18404e438cbSEd Tanous { 18504e438cbSEd Tanous ws.binary(true); 186863c1c2eSEd Tanous outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()), 187863c1c2eSEd Tanous boost::asio::buffer(msg))); 18804e438cbSEd Tanous doWrite(); 18904e438cbSEd Tanous } 19004e438cbSEd Tanous 19126ccae32SEd Tanous void sendText(std::string_view msg) override 19204e438cbSEd Tanous { 19304e438cbSEd Tanous ws.text(true); 194863c1c2eSEd Tanous outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()), 195863c1c2eSEd Tanous boost::asio::buffer(msg))); 19604e438cbSEd Tanous doWrite(); 19704e438cbSEd Tanous } 19804e438cbSEd Tanous 19904e438cbSEd Tanous void sendText(std::string&& msg) override 20004e438cbSEd Tanous { 20104e438cbSEd Tanous ws.text(true); 202863c1c2eSEd Tanous outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()), 203863c1c2eSEd Tanous boost::asio::buffer(msg))); 20404e438cbSEd Tanous doWrite(); 20504e438cbSEd Tanous } 20604e438cbSEd Tanous 20726ccae32SEd Tanous void close(std::string_view msg) override 20804e438cbSEd Tanous { 20904e438cbSEd Tanous ws.async_close( 21004e438cbSEd Tanous {boost::beast::websocket::close_code::normal, msg}, 2115e7e2dc5SEd Tanous [self(shared_from_this())](const boost::system::error_code& ec) { 21204e438cbSEd Tanous if (ec == boost::asio::error::operation_aborted) 21304e438cbSEd Tanous { 21404e438cbSEd Tanous return; 21504e438cbSEd Tanous } 21604e438cbSEd Tanous if (ec) 21704e438cbSEd Tanous { 21862598e31SEd Tanous BMCWEB_LOG_ERROR("Error closing websocket {}", ec); 21904e438cbSEd Tanous return; 22004e438cbSEd Tanous } 22104e438cbSEd Tanous }); 22204e438cbSEd Tanous } 22304e438cbSEd Tanous 224052bcbf4SNinad Palsule boost::urls::url_view url() override 225052bcbf4SNinad Palsule { 226052bcbf4SNinad Palsule return uri; 227052bcbf4SNinad Palsule } 228052bcbf4SNinad Palsule 2295ebb9d33SEd Tanous void acceptDone(const std::shared_ptr<Connection>& /*self*/, 2305ebb9d33SEd Tanous const std::unique_ptr<boost::beast::http::request< 2315ebb9d33SEd Tanous boost::beast::http::string_body>>& /*req*/, 2325ebb9d33SEd Tanous const boost::system::error_code& ec) 23304e438cbSEd Tanous { 2345ebb9d33SEd Tanous if (ec) 2355ebb9d33SEd Tanous { 2365ebb9d33SEd Tanous BMCWEB_LOG_ERROR("Error in ws.async_accept {}", ec); 2375ebb9d33SEd Tanous return; 2385ebb9d33SEd Tanous } 23962598e31SEd Tanous BMCWEB_LOG_DEBUG("Websocket accepted connection"); 24004e438cbSEd Tanous 24104e438cbSEd Tanous if (openHandler) 24204e438cbSEd Tanous { 2437772638eSzhanghch05 openHandler(*this); 24404e438cbSEd Tanous } 245863c1c2eSEd Tanous doRead(); 246863c1c2eSEd Tanous } 247863c1c2eSEd Tanous 248863c1c2eSEd Tanous void deferRead() override 249863c1c2eSEd Tanous { 250863c1c2eSEd Tanous readingDefered = true; 251863c1c2eSEd Tanous 252863c1c2eSEd Tanous // If we're not actively reading, we need to take ownership of 253863c1c2eSEd Tanous // ourselves for a small portion of time, do that, and clear when we 254863c1c2eSEd Tanous // resume. 255863c1c2eSEd Tanous selfOwned = shared_from_this(); 256863c1c2eSEd Tanous } 257863c1c2eSEd Tanous 258863c1c2eSEd Tanous void resumeRead() override 259863c1c2eSEd Tanous { 260863c1c2eSEd Tanous readingDefered = false; 261863c1c2eSEd Tanous doRead(); 262863c1c2eSEd Tanous 263863c1c2eSEd Tanous // No longer need to keep ourselves alive now that read is active. 264863c1c2eSEd Tanous selfOwned.reset(); 26504e438cbSEd Tanous } 26604e438cbSEd Tanous 26704e438cbSEd Tanous void doRead() 26804e438cbSEd Tanous { 269863c1c2eSEd Tanous if (readingDefered) 270863c1c2eSEd Tanous { 271863c1c2eSEd Tanous return; 272863c1c2eSEd Tanous } 273863c1c2eSEd Tanous ws.async_read(inBuffer, [this, self(shared_from_this())]( 274863c1c2eSEd Tanous const boost::beast::error_code& ec, 275863c1c2eSEd Tanous size_t bytesRead) { 27604e438cbSEd Tanous if (ec) 27704e438cbSEd Tanous { 27804e438cbSEd Tanous if (ec != boost::beast::websocket::error::closed) 27904e438cbSEd Tanous { 28062598e31SEd Tanous BMCWEB_LOG_ERROR("doRead error {}", ec); 28104e438cbSEd Tanous } 28204e438cbSEd Tanous if (closeHandler) 28304e438cbSEd Tanous { 284079360aeSEd Tanous std::string reason{ws.reason().reason.c_str()}; 285079360aeSEd Tanous closeHandler(*this, reason); 28604e438cbSEd Tanous } 28704e438cbSEd Tanous return; 28804e438cbSEd Tanous } 289863c1c2eSEd Tanous 290863c1c2eSEd Tanous handleMessage(bytesRead); 29104e438cbSEd Tanous }); 29204e438cbSEd Tanous } 29304e438cbSEd Tanous void doWrite() 29404e438cbSEd Tanous { 29504e438cbSEd Tanous // If we're already doing a write, ignore the request, it will be picked 29604e438cbSEd Tanous // up when the current write is complete 29704e438cbSEd Tanous if (doingWrite) 29804e438cbSEd Tanous { 29904e438cbSEd Tanous return; 30004e438cbSEd Tanous } 30104e438cbSEd Tanous 302863c1c2eSEd Tanous if (outBuffer.size() == 0) 30304e438cbSEd Tanous { 30404e438cbSEd Tanous // Done for now 30504e438cbSEd Tanous return; 30604e438cbSEd Tanous } 30704e438cbSEd Tanous doingWrite = true; 308863c1c2eSEd Tanous ws.async_write(outBuffer.data(), [this, self(shared_from_this())]( 309863c1c2eSEd Tanous const boost::beast::error_code& ec, 310863c1c2eSEd Tanous size_t bytesSent) { 31104e438cbSEd Tanous doingWrite = false; 312863c1c2eSEd Tanous outBuffer.consume(bytesSent); 31304e438cbSEd Tanous if (ec == boost::beast::websocket::error::closed) 31404e438cbSEd Tanous { 31504e438cbSEd Tanous // Do nothing here. doRead handler will call the 31604e438cbSEd Tanous // closeHandler. 31704e438cbSEd Tanous close("Write error"); 31804e438cbSEd Tanous return; 31904e438cbSEd Tanous } 32004e438cbSEd Tanous if (ec) 32104e438cbSEd Tanous { 32262598e31SEd Tanous BMCWEB_LOG_ERROR("Error in ws.async_write {}", ec); 32304e438cbSEd Tanous return; 32404e438cbSEd Tanous } 32504e438cbSEd Tanous doWrite(); 32604e438cbSEd Tanous }); 32704e438cbSEd Tanous } 32804e438cbSEd Tanous 32904e438cbSEd Tanous private: 330863c1c2eSEd Tanous void handleMessage(size_t bytesRead) 331863c1c2eSEd Tanous { 332863c1c2eSEd Tanous if (messageExHandler) 333863c1c2eSEd Tanous { 334863c1c2eSEd Tanous // Note, because of the interactions with the read buffers, 335863c1c2eSEd Tanous // this message handler overrides the normal message handler 336863c1c2eSEd Tanous messageExHandler(*this, inString, MessageType::Binary, 337863c1c2eSEd Tanous [this, self(shared_from_this()), bytesRead]() { 338863c1c2eSEd Tanous if (self == nullptr) 339863c1c2eSEd Tanous { 340863c1c2eSEd Tanous return; 341863c1c2eSEd Tanous } 342863c1c2eSEd Tanous 343863c1c2eSEd Tanous inBuffer.consume(bytesRead); 344863c1c2eSEd Tanous inString.clear(); 345863c1c2eSEd Tanous 346863c1c2eSEd Tanous doRead(); 347863c1c2eSEd Tanous }); 348863c1c2eSEd Tanous return; 349863c1c2eSEd Tanous } 350863c1c2eSEd Tanous 351863c1c2eSEd Tanous if (messageHandler) 352863c1c2eSEd Tanous { 353863c1c2eSEd Tanous messageHandler(*this, inString, ws.got_text()); 354863c1c2eSEd Tanous } 355863c1c2eSEd Tanous inBuffer.consume(bytesRead); 356863c1c2eSEd Tanous inString.clear(); 357863c1c2eSEd Tanous doRead(); 358863c1c2eSEd Tanous } 359863c1c2eSEd Tanous 360052bcbf4SNinad Palsule boost::urls::url uri; 361052bcbf4SNinad Palsule 3622aee6ca2SEd Tanous boost::beast::websocket::stream<Adaptor, false> ws; 36304e438cbSEd Tanous 364863c1c2eSEd Tanous bool readingDefered = false; 36504e438cbSEd Tanous std::string inString; 36604e438cbSEd Tanous boost::asio::dynamic_string_buffer<std::string::value_type, 36704e438cbSEd Tanous std::string::traits_type, 36804e438cbSEd Tanous std::string::allocator_type> 36904e438cbSEd Tanous inBuffer; 370863c1c2eSEd Tanous 371863c1c2eSEd Tanous boost::beast::multi_buffer outBuffer; 37204e438cbSEd Tanous bool doingWrite = false; 37304e438cbSEd Tanous 3747772638eSzhanghch05 std::function<void(Connection&)> openHandler; 37504e438cbSEd Tanous std::function<void(Connection&, const std::string&, bool)> messageHandler; 376863c1c2eSEd Tanous std::function<void(crow::websocket::Connection&, std::string_view, 377863c1c2eSEd Tanous crow::websocket::MessageType type, 378863c1c2eSEd Tanous std::function<void()>&& whenComplete)> 379863c1c2eSEd Tanous messageExHandler; 38004e438cbSEd Tanous std::function<void(Connection&, const std::string&)> closeHandler; 38104e438cbSEd Tanous std::function<void(Connection&)> errorHandler; 38204e438cbSEd Tanous std::shared_ptr<persistent_data::UserSession> session; 383863c1c2eSEd Tanous 384863c1c2eSEd Tanous std::shared_ptr<Connection> selfOwned; 38504e438cbSEd Tanous }; 38604e438cbSEd Tanous } // namespace websocket 38704e438cbSEd Tanous } // namespace crow 388