104e438cbSEd Tanous #pragma once 204e438cbSEd Tanous #include "http_request.hpp" 304e438cbSEd Tanous 404e438cbSEd Tanous #include <async_resp.hpp> 504e438cbSEd Tanous #include <boost/algorithm/string/predicate.hpp> 604e438cbSEd Tanous #include <boost/asio/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 2104e438cbSEd Tanous struct Connection : std::enable_shared_from_this<Connection> 2204e438cbSEd Tanous { 2304e438cbSEd Tanous public: 2404e438cbSEd Tanous explicit Connection(const crow::Request& reqIn) : 2504e438cbSEd Tanous req(reqIn.req), userdataPtr(nullptr) 2604e438cbSEd Tanous {} 2704e438cbSEd Tanous 2804e438cbSEd Tanous explicit Connection(const crow::Request& reqIn, std::string user) : 2904e438cbSEd Tanous req(reqIn.req), userName{std::move(user)}, userdataPtr(nullptr) 3004e438cbSEd Tanous {} 3104e438cbSEd Tanous 3204e438cbSEd Tanous virtual void sendBinary(const std::string_view msg) = 0; 3304e438cbSEd Tanous virtual void sendBinary(std::string&& msg) = 0; 3404e438cbSEd Tanous virtual void sendText(const std::string_view msg) = 0; 3504e438cbSEd Tanous virtual void sendText(std::string&& msg) = 0; 3604e438cbSEd Tanous virtual void close(const std::string_view msg = "quit") = 0; 3704e438cbSEd Tanous virtual boost::asio::io_context& getIoContext() = 0; 3804e438cbSEd Tanous virtual ~Connection() = default; 3904e438cbSEd Tanous 4004e438cbSEd Tanous void userdata(void* u) 4104e438cbSEd Tanous { 4204e438cbSEd Tanous userdataPtr = u; 4304e438cbSEd Tanous } 4404e438cbSEd Tanous void* userdata() 4504e438cbSEd Tanous { 4604e438cbSEd Tanous return userdataPtr; 4704e438cbSEd Tanous } 4804e438cbSEd Tanous 4904e438cbSEd Tanous const std::string& getUserName() const 5004e438cbSEd Tanous { 5104e438cbSEd Tanous return userName; 5204e438cbSEd Tanous } 5304e438cbSEd Tanous 5404e438cbSEd Tanous boost::beast::http::request<boost::beast::http::string_body> req; 5504e438cbSEd Tanous crow::Response res; 5604e438cbSEd Tanous 5704e438cbSEd Tanous private: 5804e438cbSEd Tanous std::string userName{}; 5904e438cbSEd Tanous void* userdataPtr; 6004e438cbSEd Tanous }; 6104e438cbSEd Tanous 6204e438cbSEd Tanous template <typename Adaptor> 6304e438cbSEd Tanous class ConnectionImpl : public Connection 6404e438cbSEd Tanous { 6504e438cbSEd Tanous public: 6604e438cbSEd Tanous ConnectionImpl( 6704e438cbSEd Tanous const crow::Request& reqIn, Adaptor adaptorIn, 6804e438cbSEd Tanous std::function<void(Connection&, std::shared_ptr<bmcweb::AsyncResp>)> 6981ce609eSEd Tanous openHandler, 7004e438cbSEd Tanous std::function<void(Connection&, const std::string&, bool)> 7181ce609eSEd Tanous messageHandler, 7281ce609eSEd Tanous std::function<void(Connection&, const std::string&)> closeHandler, 7381ce609eSEd Tanous std::function<void(Connection&)> errorHandler) : 7404e438cbSEd Tanous Connection(reqIn, reqIn.session->username), 7504e438cbSEd Tanous ws(std::move(adaptorIn)), inString(), inBuffer(inString, 131088), 7681ce609eSEd Tanous openHandler(std::move(openHandler)), 7781ce609eSEd Tanous messageHandler(std::move(messageHandler)), 7881ce609eSEd Tanous closeHandler(std::move(closeHandler)), 7981ce609eSEd Tanous errorHandler(std::move(errorHandler)), session(reqIn.session) 8004e438cbSEd Tanous { 81*02bdd967Sdhineskumare /* Turn on the timeouts on websocket stream to server role */ 82*02bdd967Sdhineskumare ws.set_option(boost::beast::websocket::stream_base::timeout::suggested( 83*02bdd967Sdhineskumare boost::beast::role_type::server)); 8404e438cbSEd Tanous BMCWEB_LOG_DEBUG << "Creating new connection " << this; 8504e438cbSEd Tanous } 8604e438cbSEd Tanous 8704e438cbSEd Tanous boost::asio::io_context& getIoContext() override 8804e438cbSEd Tanous { 8904e438cbSEd Tanous return static_cast<boost::asio::io_context&>( 9004e438cbSEd Tanous ws.get_executor().context()); 9104e438cbSEd Tanous } 9204e438cbSEd Tanous 9304e438cbSEd Tanous void start() 9404e438cbSEd Tanous { 9504e438cbSEd Tanous BMCWEB_LOG_DEBUG << "starting connection " << this; 9604e438cbSEd Tanous 9704e438cbSEd Tanous using bf = boost::beast::http::field; 9804e438cbSEd Tanous 9904e438cbSEd Tanous std::string_view protocol = req[bf::sec_websocket_protocol]; 10004e438cbSEd Tanous 10104e438cbSEd Tanous ws.set_option(boost::beast::websocket::stream_base::decorator( 10204e438cbSEd Tanous [session{session}, protocol{std::string(protocol)}]( 10304e438cbSEd Tanous boost::beast::websocket::response_type& m) { 10404e438cbSEd Tanous 10504e438cbSEd Tanous #ifndef BMCWEB_INSECURE_DISABLE_CSRF_PREVENTION 106a90daf18SEd Tanous if (session != nullptr) 107a90daf18SEd Tanous { 10804e438cbSEd Tanous // use protocol for csrf checking 10904e438cbSEd Tanous if (session->cookieAuth && 11004e438cbSEd Tanous !crow::utility::constantTimeStringCompare( 11104e438cbSEd Tanous protocol, session->csrfToken)) 11204e438cbSEd Tanous { 11304e438cbSEd Tanous BMCWEB_LOG_ERROR << "Websocket CSRF error"; 11404e438cbSEd Tanous m.result(boost::beast::http::status::unauthorized); 11504e438cbSEd Tanous return; 11604e438cbSEd Tanous } 117a90daf18SEd Tanous } 11804e438cbSEd Tanous #endif 11904e438cbSEd Tanous if (!protocol.empty()) 12004e438cbSEd Tanous { 12104e438cbSEd Tanous m.insert(bf::sec_websocket_protocol, protocol); 12204e438cbSEd Tanous } 12304e438cbSEd Tanous 12404e438cbSEd Tanous m.insert(bf::strict_transport_security, "max-age=31536000; " 12504e438cbSEd Tanous "includeSubdomains; " 12604e438cbSEd Tanous "preload"); 12704e438cbSEd Tanous m.insert(bf::pragma, "no-cache"); 12804e438cbSEd Tanous m.insert(bf::cache_control, "no-Store,no-Cache"); 12904e438cbSEd Tanous m.insert("Content-Security-Policy", "default-src 'self'"); 13004e438cbSEd Tanous m.insert("X-XSS-Protection", "1; " 13104e438cbSEd Tanous "mode=block"); 13204e438cbSEd Tanous m.insert("X-Content-Type-Options", "nosniff"); 13304e438cbSEd Tanous })); 13404e438cbSEd Tanous 13504e438cbSEd Tanous // Perform the websocket upgrade 13604e438cbSEd Tanous ws.async_accept(req, [this, self(shared_from_this())]( 13704e438cbSEd Tanous boost::system::error_code ec) { 13804e438cbSEd Tanous if (ec) 13904e438cbSEd Tanous { 14004e438cbSEd Tanous BMCWEB_LOG_ERROR << "Error in ws.async_accept " << ec; 14104e438cbSEd Tanous return; 14204e438cbSEd Tanous } 14304e438cbSEd Tanous acceptDone(); 14404e438cbSEd Tanous }); 14504e438cbSEd Tanous } 14604e438cbSEd Tanous 14704e438cbSEd Tanous void sendBinary(const std::string_view msg) override 14804e438cbSEd Tanous { 14904e438cbSEd Tanous ws.binary(true); 15004e438cbSEd Tanous outBuffer.emplace_back(msg); 15104e438cbSEd Tanous doWrite(); 15204e438cbSEd Tanous } 15304e438cbSEd Tanous 15404e438cbSEd Tanous void sendBinary(std::string&& msg) override 15504e438cbSEd Tanous { 15604e438cbSEd Tanous ws.binary(true); 15704e438cbSEd Tanous outBuffer.emplace_back(std::move(msg)); 15804e438cbSEd Tanous doWrite(); 15904e438cbSEd Tanous } 16004e438cbSEd Tanous 16104e438cbSEd Tanous void sendText(const std::string_view msg) override 16204e438cbSEd Tanous { 16304e438cbSEd Tanous ws.text(true); 16404e438cbSEd Tanous outBuffer.emplace_back(msg); 16504e438cbSEd Tanous doWrite(); 16604e438cbSEd Tanous } 16704e438cbSEd Tanous 16804e438cbSEd Tanous void sendText(std::string&& msg) override 16904e438cbSEd Tanous { 17004e438cbSEd Tanous ws.text(true); 17104e438cbSEd Tanous outBuffer.emplace_back(std::move(msg)); 17204e438cbSEd Tanous doWrite(); 17304e438cbSEd Tanous } 17404e438cbSEd Tanous 17504e438cbSEd Tanous void close(const std::string_view msg) override 17604e438cbSEd Tanous { 17704e438cbSEd Tanous ws.async_close( 17804e438cbSEd Tanous {boost::beast::websocket::close_code::normal, msg}, 17904e438cbSEd Tanous [self(shared_from_this())](boost::system::error_code ec) { 18004e438cbSEd Tanous if (ec == boost::asio::error::operation_aborted) 18104e438cbSEd Tanous { 18204e438cbSEd Tanous return; 18304e438cbSEd Tanous } 18404e438cbSEd Tanous if (ec) 18504e438cbSEd Tanous { 18604e438cbSEd Tanous BMCWEB_LOG_ERROR << "Error closing websocket " << ec; 18704e438cbSEd Tanous return; 18804e438cbSEd Tanous } 18904e438cbSEd Tanous }); 19004e438cbSEd Tanous } 19104e438cbSEd Tanous 19204e438cbSEd Tanous void acceptDone() 19304e438cbSEd Tanous { 19404e438cbSEd Tanous BMCWEB_LOG_DEBUG << "Websocket accepted connection"; 19504e438cbSEd Tanous 19604e438cbSEd Tanous auto asyncResp = std::make_shared<bmcweb::AsyncResp>( 19704e438cbSEd Tanous res, [this, self(shared_from_this())]() { doRead(); }); 19804e438cbSEd Tanous 19904e438cbSEd Tanous asyncResp->res.result(boost::beast::http::status::ok); 20004e438cbSEd Tanous 20104e438cbSEd Tanous if (openHandler) 20204e438cbSEd Tanous { 20304e438cbSEd Tanous openHandler(*this, asyncResp); 20404e438cbSEd Tanous } 20504e438cbSEd Tanous } 20604e438cbSEd Tanous 20704e438cbSEd Tanous void doRead() 20804e438cbSEd Tanous { 20904e438cbSEd Tanous ws.async_read(inBuffer, 21004e438cbSEd Tanous [this, self(shared_from_this())]( 21181ce609eSEd Tanous boost::beast::error_code ec, std::size_t bytesRead) { 21204e438cbSEd Tanous if (ec) 21304e438cbSEd Tanous { 21404e438cbSEd Tanous if (ec != boost::beast::websocket::error::closed) 21504e438cbSEd Tanous { 21604e438cbSEd Tanous BMCWEB_LOG_ERROR << "doRead error " << ec; 21704e438cbSEd Tanous } 21804e438cbSEd Tanous if (closeHandler) 21904e438cbSEd Tanous { 22004e438cbSEd Tanous std::string_view reason = ws.reason().reason; 22104e438cbSEd Tanous closeHandler(*this, std::string(reason)); 22204e438cbSEd Tanous } 22304e438cbSEd Tanous return; 22404e438cbSEd Tanous } 22504e438cbSEd Tanous if (messageHandler) 22604e438cbSEd Tanous { 22704e438cbSEd Tanous messageHandler(*this, inString, ws.got_text()); 22804e438cbSEd Tanous } 22981ce609eSEd Tanous inBuffer.consume(bytesRead); 23004e438cbSEd Tanous inString.clear(); 23104e438cbSEd Tanous doRead(); 23204e438cbSEd Tanous }); 23304e438cbSEd Tanous } 23404e438cbSEd Tanous 23504e438cbSEd Tanous void doWrite() 23604e438cbSEd Tanous { 23704e438cbSEd Tanous // If we're already doing a write, ignore the request, it will be picked 23804e438cbSEd Tanous // up when the current write is complete 23904e438cbSEd Tanous if (doingWrite) 24004e438cbSEd Tanous { 24104e438cbSEd Tanous return; 24204e438cbSEd Tanous } 24304e438cbSEd Tanous 24404e438cbSEd Tanous if (outBuffer.empty()) 24504e438cbSEd Tanous { 24604e438cbSEd Tanous // Done for now 24704e438cbSEd Tanous return; 24804e438cbSEd Tanous } 24904e438cbSEd Tanous doingWrite = true; 25004e438cbSEd Tanous ws.async_write(boost::asio::buffer(outBuffer.front()), 25104e438cbSEd Tanous [this, self(shared_from_this())]( 25204e438cbSEd Tanous boost::beast::error_code ec, std::size_t) { 25304e438cbSEd Tanous doingWrite = false; 25404e438cbSEd Tanous outBuffer.erase(outBuffer.begin()); 25504e438cbSEd Tanous if (ec == boost::beast::websocket::error::closed) 25604e438cbSEd Tanous { 25704e438cbSEd Tanous // Do nothing here. doRead handler will call the 25804e438cbSEd Tanous // closeHandler. 25904e438cbSEd Tanous close("Write error"); 26004e438cbSEd Tanous return; 26104e438cbSEd Tanous } 26204e438cbSEd Tanous if (ec) 26304e438cbSEd Tanous { 26404e438cbSEd Tanous BMCWEB_LOG_ERROR << "Error in ws.async_write " 26504e438cbSEd Tanous << ec; 26604e438cbSEd Tanous return; 26704e438cbSEd Tanous } 26804e438cbSEd Tanous doWrite(); 26904e438cbSEd Tanous }); 27004e438cbSEd Tanous } 27104e438cbSEd Tanous 27204e438cbSEd Tanous private: 2732aee6ca2SEd Tanous boost::beast::websocket::stream<Adaptor, false> ws; 27404e438cbSEd Tanous 27504e438cbSEd Tanous std::string inString; 27604e438cbSEd Tanous boost::asio::dynamic_string_buffer<std::string::value_type, 27704e438cbSEd Tanous std::string::traits_type, 27804e438cbSEd Tanous std::string::allocator_type> 27904e438cbSEd Tanous inBuffer; 28004e438cbSEd Tanous std::vector<std::string> outBuffer; 28104e438cbSEd Tanous bool doingWrite = false; 28204e438cbSEd Tanous 28304e438cbSEd Tanous std::function<void(Connection&, std::shared_ptr<bmcweb::AsyncResp>)> 28404e438cbSEd Tanous openHandler; 28504e438cbSEd Tanous std::function<void(Connection&, const std::string&, bool)> messageHandler; 28604e438cbSEd Tanous std::function<void(Connection&, const std::string&)> closeHandler; 28704e438cbSEd Tanous std::function<void(Connection&)> errorHandler; 28804e438cbSEd Tanous std::shared_ptr<persistent_data::UserSession> session; 28904e438cbSEd Tanous }; 29004e438cbSEd Tanous } // namespace websocket 29104e438cbSEd Tanous } // namespace crow 292