1*04e438cbSEd Tanous #pragma once 2*04e438cbSEd Tanous #include "http_request.hpp" 3*04e438cbSEd Tanous 4*04e438cbSEd Tanous #include <async_resp.hpp> 5*04e438cbSEd Tanous #include <boost/algorithm/string/predicate.hpp> 6*04e438cbSEd Tanous #include <boost/asio/buffer.hpp> 7*04e438cbSEd Tanous #include <boost/beast/websocket.hpp> 8*04e438cbSEd Tanous 9*04e438cbSEd Tanous #include <array> 10*04e438cbSEd Tanous #include <functional> 11*04e438cbSEd Tanous 12*04e438cbSEd Tanous #ifdef BMCWEB_ENABLE_SSL 13*04e438cbSEd Tanous #include <boost/beast/websocket/ssl.hpp> 14*04e438cbSEd Tanous #endif 15*04e438cbSEd Tanous 16*04e438cbSEd Tanous namespace crow 17*04e438cbSEd Tanous { 18*04e438cbSEd Tanous namespace websocket 19*04e438cbSEd Tanous { 20*04e438cbSEd Tanous 21*04e438cbSEd Tanous struct Connection : std::enable_shared_from_this<Connection> 22*04e438cbSEd Tanous { 23*04e438cbSEd Tanous public: 24*04e438cbSEd Tanous explicit Connection(const crow::Request& reqIn) : 25*04e438cbSEd Tanous req(reqIn.req), userdataPtr(nullptr) 26*04e438cbSEd Tanous {} 27*04e438cbSEd Tanous 28*04e438cbSEd Tanous explicit Connection(const crow::Request& reqIn, std::string user) : 29*04e438cbSEd Tanous req(reqIn.req), userName{std::move(user)}, userdataPtr(nullptr) 30*04e438cbSEd Tanous {} 31*04e438cbSEd Tanous 32*04e438cbSEd Tanous virtual void sendBinary(const std::string_view msg) = 0; 33*04e438cbSEd Tanous virtual void sendBinary(std::string&& msg) = 0; 34*04e438cbSEd Tanous virtual void sendText(const std::string_view msg) = 0; 35*04e438cbSEd Tanous virtual void sendText(std::string&& msg) = 0; 36*04e438cbSEd Tanous virtual void close(const std::string_view msg = "quit") = 0; 37*04e438cbSEd Tanous virtual boost::asio::io_context& getIoContext() = 0; 38*04e438cbSEd Tanous virtual ~Connection() = default; 39*04e438cbSEd Tanous 40*04e438cbSEd Tanous void userdata(void* u) 41*04e438cbSEd Tanous { 42*04e438cbSEd Tanous userdataPtr = u; 43*04e438cbSEd Tanous } 44*04e438cbSEd Tanous void* userdata() 45*04e438cbSEd Tanous { 46*04e438cbSEd Tanous return userdataPtr; 47*04e438cbSEd Tanous } 48*04e438cbSEd Tanous 49*04e438cbSEd Tanous const std::string& getUserName() const 50*04e438cbSEd Tanous { 51*04e438cbSEd Tanous return userName; 52*04e438cbSEd Tanous } 53*04e438cbSEd Tanous 54*04e438cbSEd Tanous boost::beast::http::request<boost::beast::http::string_body> req; 55*04e438cbSEd Tanous crow::Response res; 56*04e438cbSEd Tanous 57*04e438cbSEd Tanous private: 58*04e438cbSEd Tanous std::string userName{}; 59*04e438cbSEd Tanous void* userdataPtr; 60*04e438cbSEd Tanous }; 61*04e438cbSEd Tanous 62*04e438cbSEd Tanous template <typename Adaptor> 63*04e438cbSEd Tanous class ConnectionImpl : public Connection 64*04e438cbSEd Tanous { 65*04e438cbSEd Tanous public: 66*04e438cbSEd Tanous ConnectionImpl( 67*04e438cbSEd Tanous const crow::Request& reqIn, Adaptor adaptorIn, 68*04e438cbSEd Tanous std::function<void(Connection&, std::shared_ptr<bmcweb::AsyncResp>)> 69*04e438cbSEd Tanous open_handler, 70*04e438cbSEd Tanous std::function<void(Connection&, const std::string&, bool)> 71*04e438cbSEd Tanous message_handler, 72*04e438cbSEd Tanous std::function<void(Connection&, const std::string&)> close_handler, 73*04e438cbSEd Tanous std::function<void(Connection&)> error_handler) : 74*04e438cbSEd Tanous Connection(reqIn, reqIn.session->username), 75*04e438cbSEd Tanous ws(std::move(adaptorIn)), inString(), inBuffer(inString, 131088), 76*04e438cbSEd Tanous openHandler(std::move(open_handler)), 77*04e438cbSEd Tanous messageHandler(std::move(message_handler)), 78*04e438cbSEd Tanous closeHandler(std::move(close_handler)), 79*04e438cbSEd Tanous errorHandler(std::move(error_handler)), session(reqIn.session) 80*04e438cbSEd Tanous { 81*04e438cbSEd Tanous BMCWEB_LOG_DEBUG << "Creating new connection " << this; 82*04e438cbSEd Tanous } 83*04e438cbSEd Tanous 84*04e438cbSEd Tanous boost::asio::io_context& getIoContext() override 85*04e438cbSEd Tanous { 86*04e438cbSEd Tanous return static_cast<boost::asio::io_context&>( 87*04e438cbSEd Tanous ws.get_executor().context()); 88*04e438cbSEd Tanous } 89*04e438cbSEd Tanous 90*04e438cbSEd Tanous void start() 91*04e438cbSEd Tanous { 92*04e438cbSEd Tanous BMCWEB_LOG_DEBUG << "starting connection " << this; 93*04e438cbSEd Tanous 94*04e438cbSEd Tanous using bf = boost::beast::http::field; 95*04e438cbSEd Tanous 96*04e438cbSEd Tanous std::string_view protocol = req[bf::sec_websocket_protocol]; 97*04e438cbSEd Tanous 98*04e438cbSEd Tanous ws.set_option(boost::beast::websocket::stream_base::decorator( 99*04e438cbSEd Tanous [session{session}, protocol{std::string(protocol)}]( 100*04e438cbSEd Tanous boost::beast::websocket::response_type& m) { 101*04e438cbSEd Tanous 102*04e438cbSEd Tanous #ifndef BMCWEB_INSECURE_DISABLE_CSRF_PREVENTION 103*04e438cbSEd Tanous // use protocol for csrf checking 104*04e438cbSEd Tanous if (session->cookieAuth && 105*04e438cbSEd Tanous !crow::utility::constantTimeStringCompare( 106*04e438cbSEd Tanous protocol, session->csrfToken)) 107*04e438cbSEd Tanous { 108*04e438cbSEd Tanous BMCWEB_LOG_ERROR << "Websocket CSRF error"; 109*04e438cbSEd Tanous m.result(boost::beast::http::status::unauthorized); 110*04e438cbSEd Tanous return; 111*04e438cbSEd Tanous } 112*04e438cbSEd Tanous #endif 113*04e438cbSEd Tanous if (!protocol.empty()) 114*04e438cbSEd Tanous { 115*04e438cbSEd Tanous m.insert(bf::sec_websocket_protocol, protocol); 116*04e438cbSEd Tanous } 117*04e438cbSEd Tanous 118*04e438cbSEd Tanous m.insert(bf::strict_transport_security, "max-age=31536000; " 119*04e438cbSEd Tanous "includeSubdomains; " 120*04e438cbSEd Tanous "preload"); 121*04e438cbSEd Tanous m.insert(bf::pragma, "no-cache"); 122*04e438cbSEd Tanous m.insert(bf::cache_control, "no-Store,no-Cache"); 123*04e438cbSEd Tanous m.insert("Content-Security-Policy", "default-src 'self'"); 124*04e438cbSEd Tanous m.insert("X-XSS-Protection", "1; " 125*04e438cbSEd Tanous "mode=block"); 126*04e438cbSEd Tanous m.insert("X-Content-Type-Options", "nosniff"); 127*04e438cbSEd Tanous })); 128*04e438cbSEd Tanous 129*04e438cbSEd Tanous // Perform the websocket upgrade 130*04e438cbSEd Tanous ws.async_accept(req, [this, self(shared_from_this())]( 131*04e438cbSEd Tanous boost::system::error_code ec) { 132*04e438cbSEd Tanous if (ec) 133*04e438cbSEd Tanous { 134*04e438cbSEd Tanous BMCWEB_LOG_ERROR << "Error in ws.async_accept " << ec; 135*04e438cbSEd Tanous return; 136*04e438cbSEd Tanous } 137*04e438cbSEd Tanous acceptDone(); 138*04e438cbSEd Tanous }); 139*04e438cbSEd Tanous } 140*04e438cbSEd Tanous 141*04e438cbSEd Tanous void sendBinary(const std::string_view msg) override 142*04e438cbSEd Tanous { 143*04e438cbSEd Tanous ws.binary(true); 144*04e438cbSEd Tanous outBuffer.emplace_back(msg); 145*04e438cbSEd Tanous doWrite(); 146*04e438cbSEd Tanous } 147*04e438cbSEd Tanous 148*04e438cbSEd Tanous void sendBinary(std::string&& msg) override 149*04e438cbSEd Tanous { 150*04e438cbSEd Tanous ws.binary(true); 151*04e438cbSEd Tanous outBuffer.emplace_back(std::move(msg)); 152*04e438cbSEd Tanous doWrite(); 153*04e438cbSEd Tanous } 154*04e438cbSEd Tanous 155*04e438cbSEd Tanous void sendText(const std::string_view msg) override 156*04e438cbSEd Tanous { 157*04e438cbSEd Tanous ws.text(true); 158*04e438cbSEd Tanous outBuffer.emplace_back(msg); 159*04e438cbSEd Tanous doWrite(); 160*04e438cbSEd Tanous } 161*04e438cbSEd Tanous 162*04e438cbSEd Tanous void sendText(std::string&& msg) override 163*04e438cbSEd Tanous { 164*04e438cbSEd Tanous ws.text(true); 165*04e438cbSEd Tanous outBuffer.emplace_back(std::move(msg)); 166*04e438cbSEd Tanous doWrite(); 167*04e438cbSEd Tanous } 168*04e438cbSEd Tanous 169*04e438cbSEd Tanous void close(const std::string_view msg) override 170*04e438cbSEd Tanous { 171*04e438cbSEd Tanous ws.async_close( 172*04e438cbSEd Tanous {boost::beast::websocket::close_code::normal, msg}, 173*04e438cbSEd Tanous [self(shared_from_this())](boost::system::error_code ec) { 174*04e438cbSEd Tanous if (ec == boost::asio::error::operation_aborted) 175*04e438cbSEd Tanous { 176*04e438cbSEd Tanous return; 177*04e438cbSEd Tanous } 178*04e438cbSEd Tanous if (ec) 179*04e438cbSEd Tanous { 180*04e438cbSEd Tanous BMCWEB_LOG_ERROR << "Error closing websocket " << ec; 181*04e438cbSEd Tanous return; 182*04e438cbSEd Tanous } 183*04e438cbSEd Tanous }); 184*04e438cbSEd Tanous } 185*04e438cbSEd Tanous 186*04e438cbSEd Tanous void acceptDone() 187*04e438cbSEd Tanous { 188*04e438cbSEd Tanous BMCWEB_LOG_DEBUG << "Websocket accepted connection"; 189*04e438cbSEd Tanous 190*04e438cbSEd Tanous auto asyncResp = std::make_shared<bmcweb::AsyncResp>( 191*04e438cbSEd Tanous res, [this, self(shared_from_this())]() { doRead(); }); 192*04e438cbSEd Tanous 193*04e438cbSEd Tanous asyncResp->res.result(boost::beast::http::status::ok); 194*04e438cbSEd Tanous 195*04e438cbSEd Tanous if (openHandler) 196*04e438cbSEd Tanous { 197*04e438cbSEd Tanous openHandler(*this, asyncResp); 198*04e438cbSEd Tanous } 199*04e438cbSEd Tanous } 200*04e438cbSEd Tanous 201*04e438cbSEd Tanous void doRead() 202*04e438cbSEd Tanous { 203*04e438cbSEd Tanous ws.async_read(inBuffer, 204*04e438cbSEd Tanous [this, self(shared_from_this())]( 205*04e438cbSEd Tanous boost::beast::error_code ec, std::size_t bytes_read) { 206*04e438cbSEd Tanous if (ec) 207*04e438cbSEd Tanous { 208*04e438cbSEd Tanous if (ec != boost::beast::websocket::error::closed) 209*04e438cbSEd Tanous { 210*04e438cbSEd Tanous BMCWEB_LOG_ERROR << "doRead error " << ec; 211*04e438cbSEd Tanous } 212*04e438cbSEd Tanous if (closeHandler) 213*04e438cbSEd Tanous { 214*04e438cbSEd Tanous std::string_view reason = ws.reason().reason; 215*04e438cbSEd Tanous closeHandler(*this, std::string(reason)); 216*04e438cbSEd Tanous } 217*04e438cbSEd Tanous return; 218*04e438cbSEd Tanous } 219*04e438cbSEd Tanous if (messageHandler) 220*04e438cbSEd Tanous { 221*04e438cbSEd Tanous messageHandler(*this, inString, ws.got_text()); 222*04e438cbSEd Tanous } 223*04e438cbSEd Tanous inBuffer.consume(bytes_read); 224*04e438cbSEd Tanous inString.clear(); 225*04e438cbSEd Tanous doRead(); 226*04e438cbSEd Tanous }); 227*04e438cbSEd Tanous } 228*04e438cbSEd Tanous 229*04e438cbSEd Tanous void doWrite() 230*04e438cbSEd Tanous { 231*04e438cbSEd Tanous // If we're already doing a write, ignore the request, it will be picked 232*04e438cbSEd Tanous // up when the current write is complete 233*04e438cbSEd Tanous if (doingWrite) 234*04e438cbSEd Tanous { 235*04e438cbSEd Tanous return; 236*04e438cbSEd Tanous } 237*04e438cbSEd Tanous 238*04e438cbSEd Tanous if (outBuffer.empty()) 239*04e438cbSEd Tanous { 240*04e438cbSEd Tanous // Done for now 241*04e438cbSEd Tanous return; 242*04e438cbSEd Tanous } 243*04e438cbSEd Tanous doingWrite = true; 244*04e438cbSEd Tanous ws.async_write(boost::asio::buffer(outBuffer.front()), 245*04e438cbSEd Tanous [this, self(shared_from_this())]( 246*04e438cbSEd Tanous boost::beast::error_code ec, std::size_t) { 247*04e438cbSEd Tanous doingWrite = false; 248*04e438cbSEd Tanous outBuffer.erase(outBuffer.begin()); 249*04e438cbSEd Tanous if (ec == boost::beast::websocket::error::closed) 250*04e438cbSEd Tanous { 251*04e438cbSEd Tanous // Do nothing here. doRead handler will call the 252*04e438cbSEd Tanous // closeHandler. 253*04e438cbSEd Tanous close("Write error"); 254*04e438cbSEd Tanous return; 255*04e438cbSEd Tanous } 256*04e438cbSEd Tanous if (ec) 257*04e438cbSEd Tanous { 258*04e438cbSEd Tanous BMCWEB_LOG_ERROR << "Error in ws.async_write " 259*04e438cbSEd Tanous << ec; 260*04e438cbSEd Tanous return; 261*04e438cbSEd Tanous } 262*04e438cbSEd Tanous doWrite(); 263*04e438cbSEd Tanous }); 264*04e438cbSEd Tanous } 265*04e438cbSEd Tanous 266*04e438cbSEd Tanous private: 267*04e438cbSEd Tanous boost::beast::websocket::stream<Adaptor> ws; 268*04e438cbSEd Tanous 269*04e438cbSEd Tanous std::string inString; 270*04e438cbSEd Tanous boost::asio::dynamic_string_buffer<std::string::value_type, 271*04e438cbSEd Tanous std::string::traits_type, 272*04e438cbSEd Tanous std::string::allocator_type> 273*04e438cbSEd Tanous inBuffer; 274*04e438cbSEd Tanous std::vector<std::string> outBuffer; 275*04e438cbSEd Tanous bool doingWrite = false; 276*04e438cbSEd Tanous 277*04e438cbSEd Tanous std::function<void(Connection&, std::shared_ptr<bmcweb::AsyncResp>)> 278*04e438cbSEd Tanous openHandler; 279*04e438cbSEd Tanous std::function<void(Connection&, const std::string&, bool)> messageHandler; 280*04e438cbSEd Tanous std::function<void(Connection&, const std::string&)> closeHandler; 281*04e438cbSEd Tanous std::function<void(Connection&)> errorHandler; 282*04e438cbSEd Tanous std::shared_ptr<persistent_data::UserSession> session; 283*04e438cbSEd Tanous }; 284*04e438cbSEd Tanous } // namespace websocket 285*04e438cbSEd Tanous } // namespace crow 286