1*40e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0 2*40e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors 304e438cbSEd Tanous #pragma once 43ccb3adbSEd Tanous #include "async_resp.hpp" 5b2896149SEd Tanous #include "http_body.hpp" 604e438cbSEd Tanous #include "http_request.hpp" 704e438cbSEd Tanous 804e438cbSEd Tanous #include <boost/asio/buffer.hpp> 9ad6dd39bSLei YU #include <boost/asio/ssl/error.hpp> 10863c1c2eSEd Tanous #include <boost/beast/core/multi_buffer.hpp> 1104e438cbSEd Tanous #include <boost/beast/websocket.hpp> 128db83747SEd Tanous #include <boost/beast/websocket/ssl.hpp> 1304e438cbSEd Tanous 1404e438cbSEd Tanous #include <array> 1504e438cbSEd Tanous #include <functional> 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; 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; 429eb808c1SEd Tanous virtual void close(std::string_view msg = "quit") = 0; 43863c1c2eSEd Tanous virtual void deferRead() = 0; 44863c1c2eSEd Tanous virtual void resumeRead() = 0; 4504e438cbSEd Tanous virtual boost::asio::io_context& getIoContext() = 0; 4604e438cbSEd Tanous virtual ~Connection() = default; 47052bcbf4SNinad Palsule virtual boost::urls::url_view url() = 0; 4804e438cbSEd Tanous }; 4904e438cbSEd Tanous 5004e438cbSEd Tanous template <typename Adaptor> 5104e438cbSEd Tanous class ConnectionImpl : public Connection 5204e438cbSEd Tanous { 535ebb9d33SEd Tanous using self_t = ConnectionImpl<Adaptor>; 545ebb9d33SEd Tanous 5504e438cbSEd Tanous public: ConnectionImpl(const boost::urls::url_view & urlViewIn,const std::shared_ptr<persistent_data::UserSession> & sessionIn,Adaptor adaptorIn,std::function<void (Connection &)> openHandlerIn,std::function<void (Connection &,const std::string &,bool)> messageHandlerIn,std::function<void (crow::websocket::Connection &,std::string_view,crow::websocket::MessageType type,std::function<void ()> && whenComplete)> messageExHandlerIn,std::function<void (Connection &,const std::string &)> closeHandlerIn,std::function<void (Connection &)> errorHandlerIn)5604e438cbSEd Tanous ConnectionImpl( 575ebb9d33SEd Tanous const boost::urls::url_view& urlViewIn, 585ebb9d33SEd Tanous const std::shared_ptr<persistent_data::UserSession>& sessionIn, 59052bcbf4SNinad Palsule Adaptor adaptorIn, std::function<void(Connection&)> openHandlerIn, 6004e438cbSEd Tanous std::function<void(Connection&, const std::string&, bool)> 618a592810SEd Tanous messageHandlerIn, 62863c1c2eSEd Tanous std::function<void(crow::websocket::Connection&, std::string_view, 63863c1c2eSEd Tanous crow::websocket::MessageType type, 64863c1c2eSEd Tanous std::function<void()>&& whenComplete)> 65863c1c2eSEd Tanous messageExHandlerIn, 668a592810SEd Tanous std::function<void(Connection&, const std::string&)> closeHandlerIn, 678a592810SEd Tanous std::function<void(Connection&)> errorHandlerIn) : 68bd79bce8SPatrick Williams uri(urlViewIn), ws(std::move(adaptorIn)), inBuffer(inString, 131088), 698a592810SEd Tanous openHandler(std::move(openHandlerIn)), 708a592810SEd Tanous messageHandler(std::move(messageHandlerIn)), 71863c1c2eSEd Tanous messageExHandler(std::move(messageExHandlerIn)), 728a592810SEd Tanous closeHandler(std::move(closeHandlerIn)), 735ebb9d33SEd Tanous errorHandler(std::move(errorHandlerIn)), session(sessionIn) 7404e438cbSEd Tanous { 7502bdd967Sdhineskumare /* Turn on the timeouts on websocket stream to server role */ 7602bdd967Sdhineskumare ws.set_option(boost::beast::websocket::stream_base::timeout::suggested( 7702bdd967Sdhineskumare boost::beast::role_type::server)); 7862598e31SEd Tanous BMCWEB_LOG_DEBUG("Creating new connection {}", logPtr(this)); 7904e438cbSEd Tanous } 8004e438cbSEd Tanous getIoContext()8104e438cbSEd Tanous boost::asio::io_context& getIoContext() override 8204e438cbSEd Tanous { 8304e438cbSEd Tanous return static_cast<boost::asio::io_context&>( 8404e438cbSEd Tanous ws.get_executor().context()); 8504e438cbSEd Tanous } 8604e438cbSEd Tanous start(const crow::Request & req)875ebb9d33SEd Tanous void start(const crow::Request& req) 8804e438cbSEd Tanous { 8962598e31SEd Tanous BMCWEB_LOG_DEBUG("starting connection {}", logPtr(this)); 9004e438cbSEd Tanous 9104e438cbSEd Tanous using bf = boost::beast::http::field; 921873a04fSMyung Bae std::string protocolHeader{ 931873a04fSMyung Bae req.getHeaderValue(bf::sec_websocket_protocol)}; 9404e438cbSEd Tanous 9504e438cbSEd Tanous ws.set_option(boost::beast::websocket::stream_base::decorator( 965ebb9d33SEd Tanous [session{session}, 975ebb9d33SEd Tanous protocolHeader](boost::beast::websocket::response_type& m) { 9883328316SEd Tanous if constexpr (!BMCWEB_INSECURE_DISABLE_CSRF) 9983328316SEd Tanous { 100a90daf18SEd Tanous if (session != nullptr) 101a90daf18SEd Tanous { 10204e438cbSEd Tanous // use protocol for csrf checking 1037e9c08edSEd Tanous if (session->cookieAuth && 104bd79bce8SPatrick Williams !bmcweb::constantTimeStringCompare( 105bd79bce8SPatrick Williams protocolHeader, session->csrfToken)) 10604e438cbSEd Tanous { 10762598e31SEd Tanous BMCWEB_LOG_ERROR("Websocket CSRF error"); 10804e438cbSEd Tanous m.result(boost::beast::http::status::unauthorized); 10904e438cbSEd Tanous return; 11004e438cbSEd Tanous } 111a90daf18SEd Tanous } 11283328316SEd Tanous } 1135ebb9d33SEd Tanous if (!protocolHeader.empty()) 11404e438cbSEd Tanous { 1155ebb9d33SEd Tanous m.insert(bf::sec_websocket_protocol, protocolHeader); 11604e438cbSEd Tanous } 11704e438cbSEd Tanous 118bd79bce8SPatrick Williams m.insert(bf::strict_transport_security, 119bd79bce8SPatrick Williams "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. 131b2896149SEd Tanous using Body = boost::beast::http::request<bmcweb::HttpBody>; 1325ebb9d33SEd Tanous std::unique_ptr<Body> mobile = std::make_unique<Body>(req.req); 1335ebb9d33SEd Tanous Body* ptr = mobile.get(); 13404e438cbSEd Tanous // Perform the websocket upgrade 1355ebb9d33SEd Tanous ws.async_accept(*ptr, 1365ebb9d33SEd Tanous std::bind_front(&self_t::acceptDone, this, 1375ebb9d33SEd Tanous shared_from_this(), std::move(mobile))); 13804e438cbSEd Tanous } 13904e438cbSEd Tanous sendBinary(std::string_view msg)14026ccae32SEd Tanous void sendBinary(std::string_view msg) override 14104e438cbSEd Tanous { 14204e438cbSEd Tanous ws.binary(true); 143863c1c2eSEd Tanous outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()), 144863c1c2eSEd Tanous boost::asio::buffer(msg))); 14504e438cbSEd Tanous doWrite(); 14604e438cbSEd Tanous } 14704e438cbSEd Tanous sendEx(MessageType type,std::string_view msg,std::function<void ()> && onDone)148863c1c2eSEd Tanous void sendEx(MessageType type, std::string_view msg, 149863c1c2eSEd Tanous std::function<void()>&& onDone) override 150863c1c2eSEd Tanous { 151863c1c2eSEd Tanous if (doingWrite) 152863c1c2eSEd Tanous { 15362598e31SEd Tanous BMCWEB_LOG_CRITICAL( 15462598e31SEd Tanous "Cannot mix sendEx usage with sendBinary or sendText"); 155863c1c2eSEd Tanous onDone(); 156863c1c2eSEd Tanous return; 157863c1c2eSEd Tanous } 158863c1c2eSEd Tanous ws.binary(type == MessageType::Binary); 159863c1c2eSEd Tanous 160863c1c2eSEd Tanous ws.async_write(boost::asio::buffer(msg), 161863c1c2eSEd Tanous [weak(weak_from_this()), onDone{std::move(onDone)}]( 162863c1c2eSEd Tanous const boost::beast::error_code& ec, size_t) { 163863c1c2eSEd Tanous std::shared_ptr<Connection> self = weak.lock(); 164a8894201Szhaogang.0108 if (!self) 165a8894201Szhaogang.0108 { 166a8894201Szhaogang.0108 BMCWEB_LOG_ERROR("Connection went away"); 167a8894201Szhaogang.0108 return; 168a8894201Szhaogang.0108 } 169863c1c2eSEd Tanous 170863c1c2eSEd Tanous // Call the done handler regardless of whether we 171863c1c2eSEd Tanous // errored, but before we close things out 172863c1c2eSEd Tanous onDone(); 173863c1c2eSEd Tanous 174863c1c2eSEd Tanous if (ec) 175863c1c2eSEd Tanous { 176bd79bce8SPatrick Williams BMCWEB_LOG_ERROR("Error in ws.async_write {}", 177bd79bce8SPatrick Williams ec); 178863c1c2eSEd Tanous self->close("write error"); 179863c1c2eSEd Tanous } 180863c1c2eSEd Tanous }); 181863c1c2eSEd Tanous } 182863c1c2eSEd Tanous sendText(std::string_view msg)18326ccae32SEd Tanous void sendText(std::string_view msg) override 18404e438cbSEd Tanous { 18504e438cbSEd Tanous ws.text(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 close(std::string_view msg)19126ccae32SEd Tanous void close(std::string_view msg) override 19204e438cbSEd Tanous { 19304e438cbSEd Tanous ws.async_close( 19404e438cbSEd Tanous {boost::beast::websocket::close_code::normal, msg}, 1955e7e2dc5SEd Tanous [self(shared_from_this())](const boost::system::error_code& ec) { 19604e438cbSEd Tanous if (ec == boost::asio::error::operation_aborted) 19704e438cbSEd Tanous { 19804e438cbSEd Tanous return; 19904e438cbSEd Tanous } 20004e438cbSEd Tanous if (ec) 20104e438cbSEd Tanous { 20262598e31SEd Tanous BMCWEB_LOG_ERROR("Error closing websocket {}", ec); 20304e438cbSEd Tanous return; 20404e438cbSEd Tanous } 20504e438cbSEd Tanous }); 20604e438cbSEd Tanous } 20704e438cbSEd Tanous url()208052bcbf4SNinad Palsule boost::urls::url_view url() override 209052bcbf4SNinad Palsule { 210052bcbf4SNinad Palsule return uri; 211052bcbf4SNinad Palsule } 212052bcbf4SNinad Palsule acceptDone(const std::shared_ptr<Connection> &,const std::unique_ptr<boost::beast::http::request<bmcweb::HttpBody>> &,const boost::system::error_code & ec)2135ebb9d33SEd Tanous void acceptDone(const std::shared_ptr<Connection>& /*self*/, 21452e31629SEd Tanous const std::unique_ptr< 215b2896149SEd Tanous boost::beast::http::request<bmcweb::HttpBody>>& /*req*/, 2165ebb9d33SEd Tanous const boost::system::error_code& ec) 21704e438cbSEd Tanous { 2185ebb9d33SEd Tanous if (ec) 2195ebb9d33SEd Tanous { 2205ebb9d33SEd Tanous BMCWEB_LOG_ERROR("Error in ws.async_accept {}", ec); 2215ebb9d33SEd Tanous return; 2225ebb9d33SEd Tanous } 22362598e31SEd Tanous BMCWEB_LOG_DEBUG("Websocket accepted connection"); 22404e438cbSEd Tanous 22504e438cbSEd Tanous if (openHandler) 22604e438cbSEd Tanous { 2277772638eSzhanghch05 openHandler(*this); 22804e438cbSEd Tanous } 229863c1c2eSEd Tanous doRead(); 230863c1c2eSEd Tanous } 231863c1c2eSEd Tanous deferRead()232863c1c2eSEd Tanous void deferRead() override 233863c1c2eSEd Tanous { 234863c1c2eSEd Tanous readingDefered = true; 235863c1c2eSEd Tanous 236863c1c2eSEd Tanous // If we're not actively reading, we need to take ownership of 237863c1c2eSEd Tanous // ourselves for a small portion of time, do that, and clear when we 238863c1c2eSEd Tanous // resume. 239863c1c2eSEd Tanous selfOwned = shared_from_this(); 240863c1c2eSEd Tanous } 241863c1c2eSEd Tanous resumeRead()242863c1c2eSEd Tanous void resumeRead() override 243863c1c2eSEd Tanous { 244863c1c2eSEd Tanous readingDefered = false; 245863c1c2eSEd Tanous doRead(); 246863c1c2eSEd Tanous 247863c1c2eSEd Tanous // No longer need to keep ourselves alive now that read is active. 248863c1c2eSEd Tanous selfOwned.reset(); 24904e438cbSEd Tanous } 25004e438cbSEd Tanous doRead()25104e438cbSEd Tanous void doRead() 25204e438cbSEd Tanous { 253863c1c2eSEd Tanous if (readingDefered) 254863c1c2eSEd Tanous { 255863c1c2eSEd Tanous return; 256863c1c2eSEd Tanous } 257863c1c2eSEd Tanous ws.async_read(inBuffer, [this, self(shared_from_this())]( 258863c1c2eSEd Tanous const boost::beast::error_code& ec, 259863c1c2eSEd Tanous size_t bytesRead) { 26004e438cbSEd Tanous if (ec) 26104e438cbSEd Tanous { 262ad6dd39bSLei YU if (ec != boost::beast::websocket::error::closed && 263ad6dd39bSLei YU ec != boost::asio::error::eof && 264ad6dd39bSLei YU ec != boost::asio::ssl::error::stream_truncated) 26504e438cbSEd Tanous { 26662598e31SEd Tanous BMCWEB_LOG_ERROR("doRead error {}", ec); 26704e438cbSEd Tanous } 26804e438cbSEd Tanous if (closeHandler) 26904e438cbSEd Tanous { 270079360aeSEd Tanous std::string reason{ws.reason().reason.c_str()}; 271079360aeSEd Tanous closeHandler(*this, reason); 27204e438cbSEd Tanous } 27304e438cbSEd Tanous return; 27404e438cbSEd Tanous } 275863c1c2eSEd Tanous 276863c1c2eSEd Tanous handleMessage(bytesRead); 27704e438cbSEd Tanous }); 27804e438cbSEd Tanous } doWrite()27904e438cbSEd Tanous void doWrite() 28004e438cbSEd Tanous { 28104e438cbSEd Tanous // If we're already doing a write, ignore the request, it will be picked 28204e438cbSEd Tanous // up when the current write is complete 28304e438cbSEd Tanous if (doingWrite) 28404e438cbSEd Tanous { 28504e438cbSEd Tanous return; 28604e438cbSEd Tanous } 28704e438cbSEd Tanous 288863c1c2eSEd Tanous if (outBuffer.size() == 0) 28904e438cbSEd Tanous { 29004e438cbSEd Tanous // Done for now 29104e438cbSEd Tanous return; 29204e438cbSEd Tanous } 29304e438cbSEd Tanous doingWrite = true; 294863c1c2eSEd Tanous ws.async_write(outBuffer.data(), [this, self(shared_from_this())]( 295863c1c2eSEd Tanous const boost::beast::error_code& ec, 296863c1c2eSEd Tanous size_t bytesSent) { 29704e438cbSEd Tanous doingWrite = false; 298863c1c2eSEd Tanous outBuffer.consume(bytesSent); 29904e438cbSEd Tanous if (ec == boost::beast::websocket::error::closed) 30004e438cbSEd Tanous { 30104e438cbSEd Tanous // Do nothing here. doRead handler will call the 30204e438cbSEd Tanous // closeHandler. 30304e438cbSEd Tanous close("Write error"); 30404e438cbSEd Tanous return; 30504e438cbSEd Tanous } 30604e438cbSEd Tanous if (ec) 30704e438cbSEd Tanous { 30862598e31SEd Tanous BMCWEB_LOG_ERROR("Error in ws.async_write {}", ec); 30904e438cbSEd Tanous return; 31004e438cbSEd Tanous } 31104e438cbSEd Tanous doWrite(); 31204e438cbSEd Tanous }); 31304e438cbSEd Tanous } 31404e438cbSEd Tanous 31504e438cbSEd Tanous private: handleMessage(size_t bytesRead)316863c1c2eSEd Tanous void handleMessage(size_t bytesRead) 317863c1c2eSEd Tanous { 318863c1c2eSEd Tanous if (messageExHandler) 319863c1c2eSEd Tanous { 320863c1c2eSEd Tanous // Note, because of the interactions with the read buffers, 321863c1c2eSEd Tanous // this message handler overrides the normal message handler 322863c1c2eSEd Tanous messageExHandler(*this, inString, MessageType::Binary, 323863c1c2eSEd Tanous [this, self(shared_from_this()), bytesRead]() { 324863c1c2eSEd Tanous if (self == nullptr) 325863c1c2eSEd Tanous { 326863c1c2eSEd Tanous return; 327863c1c2eSEd Tanous } 328863c1c2eSEd Tanous 329863c1c2eSEd Tanous inBuffer.consume(bytesRead); 330863c1c2eSEd Tanous inString.clear(); 331863c1c2eSEd Tanous 332863c1c2eSEd Tanous doRead(); 333863c1c2eSEd Tanous }); 334863c1c2eSEd Tanous return; 335863c1c2eSEd Tanous } 336863c1c2eSEd Tanous 337863c1c2eSEd Tanous if (messageHandler) 338863c1c2eSEd Tanous { 339863c1c2eSEd Tanous messageHandler(*this, inString, ws.got_text()); 340863c1c2eSEd Tanous } 341863c1c2eSEd Tanous inBuffer.consume(bytesRead); 342863c1c2eSEd Tanous inString.clear(); 343863c1c2eSEd Tanous doRead(); 344863c1c2eSEd Tanous } 345863c1c2eSEd Tanous 346052bcbf4SNinad Palsule boost::urls::url uri; 347052bcbf4SNinad Palsule 3482aee6ca2SEd Tanous boost::beast::websocket::stream<Adaptor, false> ws; 34904e438cbSEd Tanous 350863c1c2eSEd Tanous bool readingDefered = false; 35104e438cbSEd Tanous std::string inString; 35204e438cbSEd Tanous boost::asio::dynamic_string_buffer<std::string::value_type, 35304e438cbSEd Tanous std::string::traits_type, 35404e438cbSEd Tanous std::string::allocator_type> 35504e438cbSEd Tanous inBuffer; 356863c1c2eSEd Tanous 357863c1c2eSEd Tanous boost::beast::multi_buffer outBuffer; 35804e438cbSEd Tanous bool doingWrite = false; 35904e438cbSEd Tanous 3607772638eSzhanghch05 std::function<void(Connection&)> openHandler; 36104e438cbSEd Tanous std::function<void(Connection&, const std::string&, bool)> messageHandler; 362863c1c2eSEd Tanous std::function<void(crow::websocket::Connection&, std::string_view, 363863c1c2eSEd Tanous crow::websocket::MessageType type, 364863c1c2eSEd Tanous std::function<void()>&& whenComplete)> 365863c1c2eSEd Tanous messageExHandler; 36604e438cbSEd Tanous std::function<void(Connection&, const std::string&)> closeHandler; 36704e438cbSEd Tanous std::function<void(Connection&)> errorHandler; 36804e438cbSEd Tanous std::shared_ptr<persistent_data::UserSession> session; 369863c1c2eSEd Tanous 370863c1c2eSEd Tanous std::shared_ptr<Connection> selfOwned; 37104e438cbSEd Tanous }; 37204e438cbSEd Tanous } // namespace websocket 37304e438cbSEd Tanous } // namespace crow 374