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> 6*863c1c2eSEd 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 21*863c1c2eSEd Tanous enum class MessageType 22*863c1c2eSEd Tanous { 23*863c1c2eSEd Tanous Binary, 24*863c1c2eSEd Tanous Text, 25*863c1c2eSEd Tanous }; 26*863c1c2eSEd Tanous 2704e438cbSEd Tanous struct Connection : std::enable_shared_from_this<Connection> 2804e438cbSEd Tanous { 2904e438cbSEd Tanous public: 30e551b5faSEd Tanous explicit Connection(const crow::Request& reqIn) : req(reqIn.req) 3104e438cbSEd Tanous {} 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; 40*863c1c2eSEd Tanous virtual void sendEx(MessageType type, std::string_view msg, 41*863c1c2eSEd 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; 45*863c1c2eSEd Tanous virtual void deferRead() = 0; 46*863c1c2eSEd Tanous virtual void resumeRead() = 0; 4704e438cbSEd Tanous virtual boost::asio::io_context& getIoContext() = 0; 4804e438cbSEd Tanous virtual ~Connection() = default; 4904e438cbSEd Tanous 5004e438cbSEd Tanous boost::beast::http::request<boost::beast::http::string_body> req; 5104e438cbSEd Tanous }; 5204e438cbSEd Tanous 5304e438cbSEd Tanous template <typename Adaptor> 5404e438cbSEd Tanous class ConnectionImpl : public Connection 5504e438cbSEd Tanous { 5604e438cbSEd Tanous public: 5704e438cbSEd Tanous ConnectionImpl( 5804e438cbSEd Tanous const crow::Request& reqIn, Adaptor adaptorIn, 598a592810SEd Tanous std::function<void(Connection&)> openHandlerIn, 6004e438cbSEd Tanous std::function<void(Connection&, const std::string&, bool)> 618a592810SEd Tanous messageHandlerIn, 62*863c1c2eSEd Tanous std::function<void(crow::websocket::Connection&, std::string_view, 63*863c1c2eSEd Tanous crow::websocket::MessageType type, 64*863c1c2eSEd Tanous std::function<void()>&& whenComplete)> 65*863c1c2eSEd Tanous messageExHandlerIn, 668a592810SEd Tanous std::function<void(Connection&, const std::string&)> closeHandlerIn, 678a592810SEd Tanous std::function<void(Connection&)> errorHandlerIn) : 68e551b5faSEd Tanous Connection(reqIn), 69e05aec50SEd Tanous ws(std::move(adaptorIn)), inBuffer(inString, 131088), 708a592810SEd Tanous openHandler(std::move(openHandlerIn)), 718a592810SEd Tanous messageHandler(std::move(messageHandlerIn)), 72*863c1c2eSEd Tanous messageExHandler(std::move(messageExHandlerIn)), 738a592810SEd Tanous closeHandler(std::move(closeHandlerIn)), 748a592810SEd Tanous errorHandler(std::move(errorHandlerIn)), session(reqIn.session) 7504e438cbSEd Tanous { 7602bdd967Sdhineskumare /* Turn on the timeouts on websocket stream to server role */ 7702bdd967Sdhineskumare ws.set_option(boost::beast::websocket::stream_base::timeout::suggested( 7802bdd967Sdhineskumare boost::beast::role_type::server)); 7904e438cbSEd Tanous BMCWEB_LOG_DEBUG << "Creating new connection " << this; 8004e438cbSEd Tanous } 8104e438cbSEd Tanous 8204e438cbSEd Tanous boost::asio::io_context& getIoContext() override 8304e438cbSEd Tanous { 8404e438cbSEd Tanous return static_cast<boost::asio::io_context&>( 8504e438cbSEd Tanous ws.get_executor().context()); 8604e438cbSEd Tanous } 8704e438cbSEd Tanous 8804e438cbSEd Tanous void start() 8904e438cbSEd Tanous { 9004e438cbSEd Tanous BMCWEB_LOG_DEBUG << "starting connection " << this; 9104e438cbSEd Tanous 9204e438cbSEd Tanous using bf = boost::beast::http::field; 9304e438cbSEd Tanous 9404e438cbSEd Tanous std::string_view protocol = req[bf::sec_websocket_protocol]; 9504e438cbSEd Tanous 9604e438cbSEd Tanous ws.set_option(boost::beast::websocket::stream_base::decorator( 9704e438cbSEd Tanous [session{session}, protocol{std::string(protocol)}]( 9804e438cbSEd Tanous 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 10404e438cbSEd Tanous if (session->cookieAuth && 10504e438cbSEd Tanous !crow::utility::constantTimeStringCompare( 10604e438cbSEd Tanous protocol, session->csrfToken)) 10704e438cbSEd Tanous { 10804e438cbSEd 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 11404e438cbSEd Tanous if (!protocol.empty()) 11504e438cbSEd Tanous { 11604e438cbSEd Tanous m.insert(bf::sec_websocket_protocol, protocol); 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 13004e438cbSEd Tanous // Perform the websocket upgrade 13104e438cbSEd Tanous ws.async_accept(req, [this, self(shared_from_this())]( 1325e7e2dc5SEd Tanous const boost::system::error_code& ec) { 13304e438cbSEd Tanous if (ec) 13404e438cbSEd Tanous { 13504e438cbSEd Tanous BMCWEB_LOG_ERROR << "Error in ws.async_accept " << ec; 13604e438cbSEd Tanous return; 13704e438cbSEd Tanous } 13804e438cbSEd Tanous acceptDone(); 13904e438cbSEd Tanous }); 14004e438cbSEd Tanous } 14104e438cbSEd Tanous 14226ccae32SEd Tanous void sendBinary(std::string_view msg) override 14304e438cbSEd Tanous { 14404e438cbSEd Tanous ws.binary(true); 145*863c1c2eSEd Tanous outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()), 146*863c1c2eSEd Tanous boost::asio::buffer(msg))); 14704e438cbSEd Tanous doWrite(); 14804e438cbSEd Tanous } 14904e438cbSEd Tanous 150*863c1c2eSEd Tanous void sendEx(MessageType type, std::string_view msg, 151*863c1c2eSEd Tanous std::function<void()>&& onDone) override 152*863c1c2eSEd Tanous { 153*863c1c2eSEd Tanous if (doingWrite) 154*863c1c2eSEd Tanous { 155*863c1c2eSEd Tanous BMCWEB_LOG_CRITICAL 156*863c1c2eSEd Tanous << "Cannot mix sendEx usage with sendBinary or sendText"; 157*863c1c2eSEd Tanous onDone(); 158*863c1c2eSEd Tanous return; 159*863c1c2eSEd Tanous } 160*863c1c2eSEd Tanous ws.binary(type == MessageType::Binary); 161*863c1c2eSEd Tanous 162*863c1c2eSEd Tanous ws.async_write(boost::asio::buffer(msg), 163*863c1c2eSEd Tanous [weak(weak_from_this()), onDone{std::move(onDone)}]( 164*863c1c2eSEd Tanous const boost::beast::error_code& ec, size_t) { 165*863c1c2eSEd Tanous std::shared_ptr<Connection> self = weak.lock(); 166*863c1c2eSEd Tanous 167*863c1c2eSEd Tanous // Call the done handler regardless of whether we 168*863c1c2eSEd Tanous // errored, but before we close things out 169*863c1c2eSEd Tanous onDone(); 170*863c1c2eSEd Tanous 171*863c1c2eSEd Tanous if (ec) 172*863c1c2eSEd Tanous { 173*863c1c2eSEd Tanous BMCWEB_LOG_ERROR << "Error in ws.async_write " << ec; 174*863c1c2eSEd Tanous self->close("write error"); 175*863c1c2eSEd Tanous } 176*863c1c2eSEd Tanous }); 177*863c1c2eSEd Tanous } 178*863c1c2eSEd Tanous 17904e438cbSEd Tanous void sendBinary(std::string&& msg) override 18004e438cbSEd Tanous { 18104e438cbSEd Tanous ws.binary(true); 182*863c1c2eSEd Tanous outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()), 183*863c1c2eSEd Tanous boost::asio::buffer(msg))); 18404e438cbSEd Tanous doWrite(); 18504e438cbSEd Tanous } 18604e438cbSEd Tanous 18726ccae32SEd Tanous void sendText(std::string_view msg) override 18804e438cbSEd Tanous { 18904e438cbSEd Tanous ws.text(true); 190*863c1c2eSEd Tanous outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()), 191*863c1c2eSEd Tanous boost::asio::buffer(msg))); 19204e438cbSEd Tanous doWrite(); 19304e438cbSEd Tanous } 19404e438cbSEd Tanous 19504e438cbSEd Tanous void sendText(std::string&& msg) override 19604e438cbSEd Tanous { 19704e438cbSEd Tanous ws.text(true); 198*863c1c2eSEd Tanous outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()), 199*863c1c2eSEd Tanous boost::asio::buffer(msg))); 20004e438cbSEd Tanous doWrite(); 20104e438cbSEd Tanous } 20204e438cbSEd Tanous 20326ccae32SEd Tanous void close(std::string_view msg) override 20404e438cbSEd Tanous { 20504e438cbSEd Tanous ws.async_close( 20604e438cbSEd Tanous {boost::beast::websocket::close_code::normal, msg}, 2075e7e2dc5SEd Tanous [self(shared_from_this())](const boost::system::error_code& ec) { 20804e438cbSEd Tanous if (ec == boost::asio::error::operation_aborted) 20904e438cbSEd Tanous { 21004e438cbSEd Tanous return; 21104e438cbSEd Tanous } 21204e438cbSEd Tanous if (ec) 21304e438cbSEd Tanous { 21404e438cbSEd Tanous BMCWEB_LOG_ERROR << "Error closing websocket " << ec; 21504e438cbSEd Tanous return; 21604e438cbSEd Tanous } 21704e438cbSEd Tanous }); 21804e438cbSEd Tanous } 21904e438cbSEd Tanous 22004e438cbSEd Tanous void acceptDone() 22104e438cbSEd Tanous { 22204e438cbSEd Tanous BMCWEB_LOG_DEBUG << "Websocket accepted connection"; 22304e438cbSEd Tanous 22404e438cbSEd Tanous if (openHandler) 22504e438cbSEd Tanous { 2267772638eSzhanghch05 openHandler(*this); 22704e438cbSEd Tanous } 228*863c1c2eSEd Tanous doRead(); 229*863c1c2eSEd Tanous } 230*863c1c2eSEd Tanous 231*863c1c2eSEd Tanous void deferRead() override 232*863c1c2eSEd Tanous { 233*863c1c2eSEd Tanous readingDefered = true; 234*863c1c2eSEd Tanous 235*863c1c2eSEd Tanous // If we're not actively reading, we need to take ownership of 236*863c1c2eSEd Tanous // ourselves for a small portion of time, do that, and clear when we 237*863c1c2eSEd Tanous // resume. 238*863c1c2eSEd Tanous selfOwned = shared_from_this(); 239*863c1c2eSEd Tanous } 240*863c1c2eSEd Tanous 241*863c1c2eSEd Tanous void resumeRead() override 242*863c1c2eSEd Tanous { 243*863c1c2eSEd Tanous readingDefered = false; 244*863c1c2eSEd Tanous doRead(); 245*863c1c2eSEd Tanous 246*863c1c2eSEd Tanous // No longer need to keep ourselves alive now that read is active. 247*863c1c2eSEd Tanous selfOwned.reset(); 24804e438cbSEd Tanous } 24904e438cbSEd Tanous 25004e438cbSEd Tanous void doRead() 25104e438cbSEd Tanous { 252*863c1c2eSEd Tanous if (readingDefered) 253*863c1c2eSEd Tanous { 254*863c1c2eSEd Tanous return; 255*863c1c2eSEd Tanous } 256*863c1c2eSEd Tanous ws.async_read(inBuffer, [this, self(shared_from_this())]( 257*863c1c2eSEd Tanous const boost::beast::error_code& ec, 258*863c1c2eSEd Tanous size_t bytesRead) { 25904e438cbSEd Tanous if (ec) 26004e438cbSEd Tanous { 26104e438cbSEd Tanous if (ec != boost::beast::websocket::error::closed) 26204e438cbSEd Tanous { 26304e438cbSEd Tanous BMCWEB_LOG_ERROR << "doRead error " << ec; 26404e438cbSEd Tanous } 26504e438cbSEd Tanous if (closeHandler) 26604e438cbSEd Tanous { 267079360aeSEd Tanous std::string reason{ws.reason().reason.c_str()}; 268079360aeSEd Tanous closeHandler(*this, reason); 26904e438cbSEd Tanous } 27004e438cbSEd Tanous return; 27104e438cbSEd Tanous } 272*863c1c2eSEd Tanous 273*863c1c2eSEd Tanous handleMessage(bytesRead); 27404e438cbSEd Tanous }); 27504e438cbSEd Tanous } 27604e438cbSEd Tanous void doWrite() 27704e438cbSEd Tanous { 27804e438cbSEd Tanous // If we're already doing a write, ignore the request, it will be picked 27904e438cbSEd Tanous // up when the current write is complete 28004e438cbSEd Tanous if (doingWrite) 28104e438cbSEd Tanous { 28204e438cbSEd Tanous return; 28304e438cbSEd Tanous } 28404e438cbSEd Tanous 285*863c1c2eSEd Tanous if (outBuffer.size() == 0) 28604e438cbSEd Tanous { 28704e438cbSEd Tanous // Done for now 28804e438cbSEd Tanous return; 28904e438cbSEd Tanous } 29004e438cbSEd Tanous doingWrite = true; 291*863c1c2eSEd Tanous ws.async_write(outBuffer.data(), [this, self(shared_from_this())]( 292*863c1c2eSEd Tanous const boost::beast::error_code& ec, 293*863c1c2eSEd Tanous size_t bytesSent) { 29404e438cbSEd Tanous doingWrite = false; 295*863c1c2eSEd Tanous outBuffer.consume(bytesSent); 29604e438cbSEd Tanous if (ec == boost::beast::websocket::error::closed) 29704e438cbSEd Tanous { 29804e438cbSEd Tanous // Do nothing here. doRead handler will call the 29904e438cbSEd Tanous // closeHandler. 30004e438cbSEd Tanous close("Write error"); 30104e438cbSEd Tanous return; 30204e438cbSEd Tanous } 30304e438cbSEd Tanous if (ec) 30404e438cbSEd Tanous { 305002d39b4SEd Tanous BMCWEB_LOG_ERROR << "Error in ws.async_write " << ec; 30604e438cbSEd Tanous return; 30704e438cbSEd Tanous } 30804e438cbSEd Tanous doWrite(); 30904e438cbSEd Tanous }); 31004e438cbSEd Tanous } 31104e438cbSEd Tanous 31204e438cbSEd Tanous private: 313*863c1c2eSEd Tanous void handleMessage(size_t bytesRead) 314*863c1c2eSEd Tanous { 315*863c1c2eSEd Tanous if (messageExHandler) 316*863c1c2eSEd Tanous { 317*863c1c2eSEd Tanous // Note, because of the interactions with the read buffers, 318*863c1c2eSEd Tanous // this message handler overrides the normal message handler 319*863c1c2eSEd Tanous messageExHandler(*this, inString, MessageType::Binary, 320*863c1c2eSEd Tanous [this, self(shared_from_this()), bytesRead]() { 321*863c1c2eSEd Tanous if (self == nullptr) 322*863c1c2eSEd Tanous { 323*863c1c2eSEd Tanous return; 324*863c1c2eSEd Tanous } 325*863c1c2eSEd Tanous 326*863c1c2eSEd Tanous inBuffer.consume(bytesRead); 327*863c1c2eSEd Tanous inString.clear(); 328*863c1c2eSEd Tanous 329*863c1c2eSEd Tanous doRead(); 330*863c1c2eSEd Tanous }); 331*863c1c2eSEd Tanous return; 332*863c1c2eSEd Tanous } 333*863c1c2eSEd Tanous 334*863c1c2eSEd Tanous if (messageHandler) 335*863c1c2eSEd Tanous { 336*863c1c2eSEd Tanous messageHandler(*this, inString, ws.got_text()); 337*863c1c2eSEd Tanous } 338*863c1c2eSEd Tanous inBuffer.consume(bytesRead); 339*863c1c2eSEd Tanous inString.clear(); 340*863c1c2eSEd Tanous doRead(); 341*863c1c2eSEd Tanous } 342*863c1c2eSEd Tanous 3432aee6ca2SEd Tanous boost::beast::websocket::stream<Adaptor, false> ws; 34404e438cbSEd Tanous 345*863c1c2eSEd Tanous bool readingDefered = false; 34604e438cbSEd Tanous std::string inString; 34704e438cbSEd Tanous boost::asio::dynamic_string_buffer<std::string::value_type, 34804e438cbSEd Tanous std::string::traits_type, 34904e438cbSEd Tanous std::string::allocator_type> 35004e438cbSEd Tanous inBuffer; 351*863c1c2eSEd Tanous 352*863c1c2eSEd Tanous boost::beast::multi_buffer outBuffer; 35304e438cbSEd Tanous bool doingWrite = false; 35404e438cbSEd Tanous 3557772638eSzhanghch05 std::function<void(Connection&)> openHandler; 35604e438cbSEd Tanous std::function<void(Connection&, const std::string&, bool)> messageHandler; 357*863c1c2eSEd Tanous std::function<void(crow::websocket::Connection&, std::string_view, 358*863c1c2eSEd Tanous crow::websocket::MessageType type, 359*863c1c2eSEd Tanous std::function<void()>&& whenComplete)> 360*863c1c2eSEd Tanous messageExHandler; 36104e438cbSEd Tanous std::function<void(Connection&, const std::string&)> closeHandler; 36204e438cbSEd Tanous std::function<void(Connection&)> errorHandler; 36304e438cbSEd Tanous std::shared_ptr<persistent_data::UserSession> session; 364*863c1c2eSEd Tanous 365*863c1c2eSEd Tanous std::shared_ptr<Connection> selfOwned; 36604e438cbSEd Tanous }; 36704e438cbSEd Tanous } // namespace websocket 36804e438cbSEd Tanous } // namespace crow 369