104e438cbSEd Tanous #pragma once 23ccb3adbSEd Tanous #include "async_resp.hpp" 3b2896149SEd Tanous #include "http_body.hpp" 404e438cbSEd Tanous #include "http_request.hpp" 504e438cbSEd Tanous 604e438cbSEd Tanous #include <boost/asio/buffer.hpp> 7863c1c2eSEd Tanous #include <boost/beast/core/multi_buffer.hpp> 804e438cbSEd Tanous #include <boost/beast/websocket.hpp> 904e438cbSEd Tanous 1004e438cbSEd Tanous #include <array> 1104e438cbSEd Tanous #include <functional> 1204e438cbSEd Tanous 1304e438cbSEd Tanous #ifdef BMCWEB_ENABLE_SSL 1404e438cbSEd Tanous #include <boost/beast/websocket/ssl.hpp> 1504e438cbSEd Tanous #endif 1604e438cbSEd Tanous 1704e438cbSEd Tanous namespace crow 1804e438cbSEd Tanous { 1904e438cbSEd Tanous namespace websocket 2004e438cbSEd Tanous { 2104e438cbSEd Tanous 22863c1c2eSEd Tanous enum class MessageType 23863c1c2eSEd Tanous { 24863c1c2eSEd Tanous Binary, 25863c1c2eSEd Tanous Text, 26863c1c2eSEd Tanous }; 27863c1c2eSEd Tanous 2804e438cbSEd Tanous struct Connection : std::enable_shared_from_this<Connection> 2904e438cbSEd Tanous { 3004e438cbSEd Tanous public: 315ebb9d33SEd Tanous Connection() = default; 3204e438cbSEd Tanous 33ecd6a3a2SEd Tanous Connection(const Connection&) = delete; 34ecd6a3a2SEd Tanous Connection(Connection&&) = delete; 35ecd6a3a2SEd Tanous Connection& operator=(const Connection&) = delete; 36ecd6a3a2SEd Tanous Connection& operator=(const Connection&&) = delete; 37ecd6a3a2SEd Tanous 389eb808c1SEd Tanous virtual void sendBinary(std::string_view msg) = 0; 3904e438cbSEd Tanous virtual void sendBinary(std::string&& msg) = 0; 40863c1c2eSEd Tanous virtual void sendEx(MessageType type, std::string_view msg, 41863c1c2eSEd Tanous std::function<void()>&& onDone) = 0; 429eb808c1SEd Tanous virtual void sendText(std::string_view msg) = 0; 4304e438cbSEd Tanous virtual void sendText(std::string&& msg) = 0; 449eb808c1SEd Tanous virtual void close(std::string_view msg = "quit") = 0; 45863c1c2eSEd Tanous virtual void deferRead() = 0; 46863c1c2eSEd Tanous virtual void resumeRead() = 0; 4704e438cbSEd Tanous virtual boost::asio::io_context& getIoContext() = 0; 4804e438cbSEd Tanous virtual ~Connection() = default; 49052bcbf4SNinad Palsule virtual boost::urls::url_view url() = 0; 5004e438cbSEd Tanous }; 5104e438cbSEd Tanous 5204e438cbSEd Tanous template <typename Adaptor> 5304e438cbSEd Tanous class ConnectionImpl : public Connection 5404e438cbSEd Tanous { 555ebb9d33SEd Tanous using self_t = ConnectionImpl<Adaptor>; 565ebb9d33SEd Tanous 5704e438cbSEd Tanous public: 5804e438cbSEd Tanous ConnectionImpl( 595ebb9d33SEd Tanous const boost::urls::url_view& urlViewIn, 605ebb9d33SEd Tanous const std::shared_ptr<persistent_data::UserSession>& sessionIn, 61052bcbf4SNinad Palsule Adaptor adaptorIn, std::function<void(Connection&)> openHandlerIn, 6204e438cbSEd Tanous std::function<void(Connection&, const std::string&, bool)> 638a592810SEd Tanous messageHandlerIn, 64863c1c2eSEd Tanous std::function<void(crow::websocket::Connection&, std::string_view, 65863c1c2eSEd Tanous crow::websocket::MessageType type, 66863c1c2eSEd Tanous std::function<void()>&& whenComplete)> 67863c1c2eSEd Tanous messageExHandlerIn, 688a592810SEd Tanous std::function<void(Connection&, const std::string&)> closeHandlerIn, 698a592810SEd Tanous std::function<void(Connection&)> errorHandlerIn) : 705ebb9d33SEd Tanous uri(urlViewIn), 715ebb9d33SEd Tanous ws(std::move(adaptorIn)), inBuffer(inString, 131088), 728a592810SEd Tanous openHandler(std::move(openHandlerIn)), 738a592810SEd Tanous messageHandler(std::move(messageHandlerIn)), 74863c1c2eSEd Tanous messageExHandler(std::move(messageExHandlerIn)), 758a592810SEd Tanous closeHandler(std::move(closeHandlerIn)), 765ebb9d33SEd Tanous errorHandler(std::move(errorHandlerIn)), session(sessionIn) 7704e438cbSEd Tanous { 7802bdd967Sdhineskumare /* Turn on the timeouts on websocket stream to server role */ 7902bdd967Sdhineskumare ws.set_option(boost::beast::websocket::stream_base::timeout::suggested( 8002bdd967Sdhineskumare boost::beast::role_type::server)); 8162598e31SEd Tanous BMCWEB_LOG_DEBUG("Creating new connection {}", logPtr(this)); 8204e438cbSEd Tanous } 8304e438cbSEd Tanous 8404e438cbSEd Tanous boost::asio::io_context& getIoContext() override 8504e438cbSEd Tanous { 8604e438cbSEd Tanous return static_cast<boost::asio::io_context&>( 8704e438cbSEd Tanous ws.get_executor().context()); 8804e438cbSEd Tanous } 8904e438cbSEd Tanous 905ebb9d33SEd Tanous void start(const crow::Request& req) 9104e438cbSEd Tanous { 9262598e31SEd Tanous BMCWEB_LOG_DEBUG("starting connection {}", logPtr(this)); 9304e438cbSEd Tanous 9404e438cbSEd Tanous using bf = boost::beast::http::field; 95*1873a04fSMyung Bae std::string protocolHeader{ 96*1873a04fSMyung Bae req.getHeaderValue(bf::sec_websocket_protocol)}; 9704e438cbSEd Tanous 9804e438cbSEd Tanous ws.set_option(boost::beast::websocket::stream_base::decorator( 995ebb9d33SEd Tanous [session{session}, 1005ebb9d33SEd Tanous protocolHeader](boost::beast::websocket::response_type& m) { 10104e438cbSEd Tanous 10204e438cbSEd Tanous #ifndef BMCWEB_INSECURE_DISABLE_CSRF_PREVENTION 103a90daf18SEd Tanous if (session != nullptr) 104a90daf18SEd Tanous { 10504e438cbSEd Tanous // use protocol for csrf checking 1067e9c08edSEd Tanous if (session->cookieAuth && 1077e9c08edSEd Tanous !crow::utility::constantTimeStringCompare( 1085ebb9d33SEd Tanous protocolHeader, session->csrfToken)) 10904e438cbSEd Tanous { 11062598e31SEd Tanous BMCWEB_LOG_ERROR("Websocket CSRF error"); 11104e438cbSEd Tanous m.result(boost::beast::http::status::unauthorized); 11204e438cbSEd Tanous return; 11304e438cbSEd Tanous } 114a90daf18SEd Tanous } 11504e438cbSEd Tanous #endif 1165ebb9d33SEd Tanous if (!protocolHeader.empty()) 11704e438cbSEd Tanous { 1185ebb9d33SEd Tanous m.insert(bf::sec_websocket_protocol, protocolHeader); 11904e438cbSEd Tanous } 12004e438cbSEd Tanous 12104e438cbSEd Tanous m.insert(bf::strict_transport_security, "max-age=31536000; " 12204e438cbSEd Tanous "includeSubdomains; " 12304e438cbSEd Tanous "preload"); 12404e438cbSEd Tanous m.insert(bf::pragma, "no-cache"); 12504e438cbSEd Tanous m.insert(bf::cache_control, "no-Store,no-Cache"); 12604e438cbSEd Tanous m.insert("Content-Security-Policy", "default-src 'self'"); 12704e438cbSEd Tanous m.insert("X-XSS-Protection", "1; " 12804e438cbSEd Tanous "mode=block"); 12904e438cbSEd Tanous m.insert("X-Content-Type-Options", "nosniff"); 13004e438cbSEd Tanous })); 13104e438cbSEd Tanous 1325ebb9d33SEd Tanous // Make a pointer to keep the req alive while we accept it. 133b2896149SEd Tanous using Body = boost::beast::http::request<bmcweb::HttpBody>; 1345ebb9d33SEd Tanous std::unique_ptr<Body> mobile = std::make_unique<Body>(req.req); 1355ebb9d33SEd Tanous Body* ptr = mobile.get(); 13604e438cbSEd Tanous // Perform the websocket upgrade 1375ebb9d33SEd Tanous ws.async_accept(*ptr, 1385ebb9d33SEd Tanous std::bind_front(&self_t::acceptDone, this, 1395ebb9d33SEd Tanous shared_from_this(), std::move(mobile))); 14004e438cbSEd Tanous } 14104e438cbSEd Tanous 14226ccae32SEd Tanous void sendBinary(std::string_view msg) override 14304e438cbSEd Tanous { 14404e438cbSEd Tanous ws.binary(true); 145863c1c2eSEd Tanous outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()), 146863c1c2eSEd Tanous boost::asio::buffer(msg))); 14704e438cbSEd Tanous doWrite(); 14804e438cbSEd Tanous } 14904e438cbSEd Tanous 150863c1c2eSEd Tanous void sendEx(MessageType type, std::string_view msg, 151863c1c2eSEd Tanous std::function<void()>&& onDone) override 152863c1c2eSEd Tanous { 153863c1c2eSEd Tanous if (doingWrite) 154863c1c2eSEd Tanous { 15562598e31SEd Tanous BMCWEB_LOG_CRITICAL( 15662598e31SEd Tanous "Cannot mix sendEx usage with sendBinary or sendText"); 157863c1c2eSEd Tanous onDone(); 158863c1c2eSEd Tanous return; 159863c1c2eSEd Tanous } 160863c1c2eSEd Tanous ws.binary(type == MessageType::Binary); 161863c1c2eSEd Tanous 162863c1c2eSEd Tanous ws.async_write(boost::asio::buffer(msg), 163863c1c2eSEd Tanous [weak(weak_from_this()), onDone{std::move(onDone)}]( 164863c1c2eSEd Tanous const boost::beast::error_code& ec, size_t) { 165863c1c2eSEd Tanous std::shared_ptr<Connection> self = weak.lock(); 166a8894201Szhaogang.0108 if (!self) 167a8894201Szhaogang.0108 { 168a8894201Szhaogang.0108 BMCWEB_LOG_ERROR("Connection went away"); 169a8894201Szhaogang.0108 return; 170a8894201Szhaogang.0108 } 171863c1c2eSEd Tanous 172863c1c2eSEd Tanous // Call the done handler regardless of whether we 173863c1c2eSEd Tanous // errored, but before we close things out 174863c1c2eSEd Tanous onDone(); 175863c1c2eSEd Tanous 176863c1c2eSEd Tanous if (ec) 177863c1c2eSEd Tanous { 17862598e31SEd Tanous BMCWEB_LOG_ERROR("Error in ws.async_write {}", ec); 179863c1c2eSEd Tanous self->close("write error"); 180863c1c2eSEd Tanous } 181863c1c2eSEd Tanous }); 182863c1c2eSEd Tanous } 183863c1c2eSEd Tanous 18404e438cbSEd Tanous void sendBinary(std::string&& msg) override 18504e438cbSEd Tanous { 18604e438cbSEd Tanous ws.binary(true); 187863c1c2eSEd Tanous outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()), 188863c1c2eSEd Tanous boost::asio::buffer(msg))); 18904e438cbSEd Tanous doWrite(); 19004e438cbSEd Tanous } 19104e438cbSEd Tanous 19226ccae32SEd Tanous void sendText(std::string_view msg) override 19304e438cbSEd Tanous { 19404e438cbSEd Tanous ws.text(true); 195863c1c2eSEd Tanous outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()), 196863c1c2eSEd Tanous boost::asio::buffer(msg))); 19704e438cbSEd Tanous doWrite(); 19804e438cbSEd Tanous } 19904e438cbSEd Tanous 20004e438cbSEd Tanous void sendText(std::string&& msg) override 20104e438cbSEd Tanous { 20204e438cbSEd Tanous ws.text(true); 203863c1c2eSEd Tanous outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()), 204863c1c2eSEd Tanous boost::asio::buffer(msg))); 20504e438cbSEd Tanous doWrite(); 20604e438cbSEd Tanous } 20704e438cbSEd Tanous 20826ccae32SEd Tanous void close(std::string_view msg) override 20904e438cbSEd Tanous { 21004e438cbSEd Tanous ws.async_close( 21104e438cbSEd Tanous {boost::beast::websocket::close_code::normal, msg}, 2125e7e2dc5SEd Tanous [self(shared_from_this())](const boost::system::error_code& ec) { 21304e438cbSEd Tanous if (ec == boost::asio::error::operation_aborted) 21404e438cbSEd Tanous { 21504e438cbSEd Tanous return; 21604e438cbSEd Tanous } 21704e438cbSEd Tanous if (ec) 21804e438cbSEd Tanous { 21962598e31SEd Tanous BMCWEB_LOG_ERROR("Error closing websocket {}", ec); 22004e438cbSEd Tanous return; 22104e438cbSEd Tanous } 22204e438cbSEd Tanous }); 22304e438cbSEd Tanous } 22404e438cbSEd Tanous 225052bcbf4SNinad Palsule boost::urls::url_view url() override 226052bcbf4SNinad Palsule { 227052bcbf4SNinad Palsule return uri; 228052bcbf4SNinad Palsule } 229052bcbf4SNinad Palsule 2305ebb9d33SEd Tanous void acceptDone(const std::shared_ptr<Connection>& /*self*/, 23152e31629SEd Tanous const std::unique_ptr< 232b2896149SEd Tanous boost::beast::http::request<bmcweb::HttpBody>>& /*req*/, 2335ebb9d33SEd Tanous const boost::system::error_code& ec) 23404e438cbSEd Tanous { 2355ebb9d33SEd Tanous if (ec) 2365ebb9d33SEd Tanous { 2375ebb9d33SEd Tanous BMCWEB_LOG_ERROR("Error in ws.async_accept {}", ec); 2385ebb9d33SEd Tanous return; 2395ebb9d33SEd Tanous } 24062598e31SEd Tanous BMCWEB_LOG_DEBUG("Websocket accepted connection"); 24104e438cbSEd Tanous 24204e438cbSEd Tanous if (openHandler) 24304e438cbSEd Tanous { 2447772638eSzhanghch05 openHandler(*this); 24504e438cbSEd Tanous } 246863c1c2eSEd Tanous doRead(); 247863c1c2eSEd Tanous } 248863c1c2eSEd Tanous 249863c1c2eSEd Tanous void deferRead() override 250863c1c2eSEd Tanous { 251863c1c2eSEd Tanous readingDefered = true; 252863c1c2eSEd Tanous 253863c1c2eSEd Tanous // If we're not actively reading, we need to take ownership of 254863c1c2eSEd Tanous // ourselves for a small portion of time, do that, and clear when we 255863c1c2eSEd Tanous // resume. 256863c1c2eSEd Tanous selfOwned = shared_from_this(); 257863c1c2eSEd Tanous } 258863c1c2eSEd Tanous 259863c1c2eSEd Tanous void resumeRead() override 260863c1c2eSEd Tanous { 261863c1c2eSEd Tanous readingDefered = false; 262863c1c2eSEd Tanous doRead(); 263863c1c2eSEd Tanous 264863c1c2eSEd Tanous // No longer need to keep ourselves alive now that read is active. 265863c1c2eSEd Tanous selfOwned.reset(); 26604e438cbSEd Tanous } 26704e438cbSEd Tanous 26804e438cbSEd Tanous void doRead() 26904e438cbSEd Tanous { 270863c1c2eSEd Tanous if (readingDefered) 271863c1c2eSEd Tanous { 272863c1c2eSEd Tanous return; 273863c1c2eSEd Tanous } 274863c1c2eSEd Tanous ws.async_read(inBuffer, [this, self(shared_from_this())]( 275863c1c2eSEd Tanous const boost::beast::error_code& ec, 276863c1c2eSEd Tanous size_t bytesRead) { 27704e438cbSEd Tanous if (ec) 27804e438cbSEd Tanous { 27904e438cbSEd Tanous if (ec != boost::beast::websocket::error::closed) 28004e438cbSEd Tanous { 28162598e31SEd Tanous BMCWEB_LOG_ERROR("doRead error {}", ec); 28204e438cbSEd Tanous } 28304e438cbSEd Tanous if (closeHandler) 28404e438cbSEd Tanous { 285079360aeSEd Tanous std::string reason{ws.reason().reason.c_str()}; 286079360aeSEd Tanous closeHandler(*this, reason); 28704e438cbSEd Tanous } 28804e438cbSEd Tanous return; 28904e438cbSEd Tanous } 290863c1c2eSEd Tanous 291863c1c2eSEd Tanous handleMessage(bytesRead); 29204e438cbSEd Tanous }); 29304e438cbSEd Tanous } 29404e438cbSEd Tanous void doWrite() 29504e438cbSEd Tanous { 29604e438cbSEd Tanous // If we're already doing a write, ignore the request, it will be picked 29704e438cbSEd Tanous // up when the current write is complete 29804e438cbSEd Tanous if (doingWrite) 29904e438cbSEd Tanous { 30004e438cbSEd Tanous return; 30104e438cbSEd Tanous } 30204e438cbSEd Tanous 303863c1c2eSEd Tanous if (outBuffer.size() == 0) 30404e438cbSEd Tanous { 30504e438cbSEd Tanous // Done for now 30604e438cbSEd Tanous return; 30704e438cbSEd Tanous } 30804e438cbSEd Tanous doingWrite = true; 309863c1c2eSEd Tanous ws.async_write(outBuffer.data(), [this, self(shared_from_this())]( 310863c1c2eSEd Tanous const boost::beast::error_code& ec, 311863c1c2eSEd Tanous size_t bytesSent) { 31204e438cbSEd Tanous doingWrite = false; 313863c1c2eSEd Tanous outBuffer.consume(bytesSent); 31404e438cbSEd Tanous if (ec == boost::beast::websocket::error::closed) 31504e438cbSEd Tanous { 31604e438cbSEd Tanous // Do nothing here. doRead handler will call the 31704e438cbSEd Tanous // closeHandler. 31804e438cbSEd Tanous close("Write error"); 31904e438cbSEd Tanous return; 32004e438cbSEd Tanous } 32104e438cbSEd Tanous if (ec) 32204e438cbSEd Tanous { 32362598e31SEd Tanous BMCWEB_LOG_ERROR("Error in ws.async_write {}", ec); 32404e438cbSEd Tanous return; 32504e438cbSEd Tanous } 32604e438cbSEd Tanous doWrite(); 32704e438cbSEd Tanous }); 32804e438cbSEd Tanous } 32904e438cbSEd Tanous 33004e438cbSEd Tanous private: 331863c1c2eSEd Tanous void handleMessage(size_t bytesRead) 332863c1c2eSEd Tanous { 333863c1c2eSEd Tanous if (messageExHandler) 334863c1c2eSEd Tanous { 335863c1c2eSEd Tanous // Note, because of the interactions with the read buffers, 336863c1c2eSEd Tanous // this message handler overrides the normal message handler 337863c1c2eSEd Tanous messageExHandler(*this, inString, MessageType::Binary, 338863c1c2eSEd Tanous [this, self(shared_from_this()), bytesRead]() { 339863c1c2eSEd Tanous if (self == nullptr) 340863c1c2eSEd Tanous { 341863c1c2eSEd Tanous return; 342863c1c2eSEd Tanous } 343863c1c2eSEd Tanous 344863c1c2eSEd Tanous inBuffer.consume(bytesRead); 345863c1c2eSEd Tanous inString.clear(); 346863c1c2eSEd Tanous 347863c1c2eSEd Tanous doRead(); 348863c1c2eSEd Tanous }); 349863c1c2eSEd Tanous return; 350863c1c2eSEd Tanous } 351863c1c2eSEd Tanous 352863c1c2eSEd Tanous if (messageHandler) 353863c1c2eSEd Tanous { 354863c1c2eSEd Tanous messageHandler(*this, inString, ws.got_text()); 355863c1c2eSEd Tanous } 356863c1c2eSEd Tanous inBuffer.consume(bytesRead); 357863c1c2eSEd Tanous inString.clear(); 358863c1c2eSEd Tanous doRead(); 359863c1c2eSEd Tanous } 360863c1c2eSEd Tanous 361052bcbf4SNinad Palsule boost::urls::url uri; 362052bcbf4SNinad Palsule 3632aee6ca2SEd Tanous boost::beast::websocket::stream<Adaptor, false> ws; 36404e438cbSEd Tanous 365863c1c2eSEd Tanous bool readingDefered = false; 36604e438cbSEd Tanous std::string inString; 36704e438cbSEd Tanous boost::asio::dynamic_string_buffer<std::string::value_type, 36804e438cbSEd Tanous std::string::traits_type, 36904e438cbSEd Tanous std::string::allocator_type> 37004e438cbSEd Tanous inBuffer; 371863c1c2eSEd Tanous 372863c1c2eSEd Tanous boost::beast::multi_buffer outBuffer; 37304e438cbSEd Tanous bool doingWrite = false; 37404e438cbSEd Tanous 3757772638eSzhanghch05 std::function<void(Connection&)> openHandler; 37604e438cbSEd Tanous std::function<void(Connection&, const std::string&, bool)> messageHandler; 377863c1c2eSEd Tanous std::function<void(crow::websocket::Connection&, std::string_view, 378863c1c2eSEd Tanous crow::websocket::MessageType type, 379863c1c2eSEd Tanous std::function<void()>&& whenComplete)> 380863c1c2eSEd Tanous messageExHandler; 38104e438cbSEd Tanous std::function<void(Connection&, const std::string&)> closeHandler; 38204e438cbSEd Tanous std::function<void(Connection&)> errorHandler; 38304e438cbSEd Tanous std::shared_ptr<persistent_data::UserSession> session; 384863c1c2eSEd Tanous 385863c1c2eSEd Tanous std::shared_ptr<Connection> selfOwned; 38604e438cbSEd Tanous }; 38704e438cbSEd Tanous } // namespace websocket 38804e438cbSEd Tanous } // namespace crow 389