xref: /openbmc/bmcweb/http/websocket.hpp (revision e551b5fa48a78c06441456106e35d4b2ac8642b6)
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>
604e438cbSEd Tanous #include <boost/beast/websocket.hpp>
704e438cbSEd Tanous 
804e438cbSEd Tanous #include <array>
904e438cbSEd Tanous #include <functional>
1004e438cbSEd Tanous 
1104e438cbSEd Tanous #ifdef BMCWEB_ENABLE_SSL
1204e438cbSEd Tanous #include <boost/beast/websocket/ssl.hpp>
1304e438cbSEd Tanous #endif
1404e438cbSEd Tanous 
1504e438cbSEd Tanous namespace crow
1604e438cbSEd Tanous {
1704e438cbSEd Tanous namespace websocket
1804e438cbSEd Tanous {
1904e438cbSEd Tanous 
2004e438cbSEd Tanous struct Connection : std::enable_shared_from_this<Connection>
2104e438cbSEd Tanous {
2204e438cbSEd Tanous   public:
23*e551b5faSEd Tanous     explicit Connection(const crow::Request& reqIn) : req(reqIn.req)
2404e438cbSEd Tanous     {}
2504e438cbSEd Tanous 
26ecd6a3a2SEd Tanous     Connection(const Connection&) = delete;
27ecd6a3a2SEd Tanous     Connection(Connection&&) = delete;
28ecd6a3a2SEd Tanous     Connection& operator=(const Connection&) = delete;
29ecd6a3a2SEd Tanous     Connection& operator=(const Connection&&) = delete;
30ecd6a3a2SEd Tanous 
319eb808c1SEd Tanous     virtual void sendBinary(std::string_view msg) = 0;
3204e438cbSEd Tanous     virtual void sendBinary(std::string&& msg) = 0;
339eb808c1SEd Tanous     virtual void sendText(std::string_view msg) = 0;
3404e438cbSEd Tanous     virtual void sendText(std::string&& msg) = 0;
359eb808c1SEd Tanous     virtual void close(std::string_view msg = "quit") = 0;
3604e438cbSEd Tanous     virtual boost::asio::io_context& getIoContext() = 0;
3704e438cbSEd Tanous     virtual ~Connection() = default;
3804e438cbSEd Tanous 
3904e438cbSEd Tanous     boost::beast::http::request<boost::beast::http::string_body> req;
4004e438cbSEd Tanous };
4104e438cbSEd Tanous 
4204e438cbSEd Tanous template <typename Adaptor>
4304e438cbSEd Tanous class ConnectionImpl : public Connection
4404e438cbSEd Tanous {
4504e438cbSEd Tanous   public:
4604e438cbSEd Tanous     ConnectionImpl(
4704e438cbSEd Tanous         const crow::Request& reqIn, Adaptor adaptorIn,
488a592810SEd Tanous         std::function<void(Connection&)> openHandlerIn,
4904e438cbSEd Tanous         std::function<void(Connection&, const std::string&, bool)>
508a592810SEd Tanous             messageHandlerIn,
518a592810SEd Tanous         std::function<void(Connection&, const std::string&)> closeHandlerIn,
528a592810SEd Tanous         std::function<void(Connection&)> errorHandlerIn) :
53*e551b5faSEd Tanous         Connection(reqIn),
54e05aec50SEd Tanous         ws(std::move(adaptorIn)), inBuffer(inString, 131088),
558a592810SEd Tanous         openHandler(std::move(openHandlerIn)),
568a592810SEd Tanous         messageHandler(std::move(messageHandlerIn)),
578a592810SEd Tanous         closeHandler(std::move(closeHandlerIn)),
588a592810SEd Tanous         errorHandler(std::move(errorHandlerIn)), session(reqIn.session)
5904e438cbSEd Tanous     {
6002bdd967Sdhineskumare         /* Turn on the timeouts on websocket stream to server role */
6102bdd967Sdhineskumare         ws.set_option(boost::beast::websocket::stream_base::timeout::suggested(
6202bdd967Sdhineskumare             boost::beast::role_type::server));
6304e438cbSEd Tanous         BMCWEB_LOG_DEBUG << "Creating new connection " << this;
6404e438cbSEd Tanous     }
6504e438cbSEd Tanous 
6604e438cbSEd Tanous     boost::asio::io_context& getIoContext() override
6704e438cbSEd Tanous     {
6804e438cbSEd Tanous         return static_cast<boost::asio::io_context&>(
6904e438cbSEd Tanous             ws.get_executor().context());
7004e438cbSEd Tanous     }
7104e438cbSEd Tanous 
7204e438cbSEd Tanous     void start()
7304e438cbSEd Tanous     {
7404e438cbSEd Tanous         BMCWEB_LOG_DEBUG << "starting connection " << this;
7504e438cbSEd Tanous 
7604e438cbSEd Tanous         using bf = boost::beast::http::field;
7704e438cbSEd Tanous 
7804e438cbSEd Tanous         std::string_view protocol = req[bf::sec_websocket_protocol];
7904e438cbSEd Tanous 
8004e438cbSEd Tanous         ws.set_option(boost::beast::websocket::stream_base::decorator(
8104e438cbSEd Tanous             [session{session}, protocol{std::string(protocol)}](
8204e438cbSEd Tanous                 boost::beast::websocket::response_type& m) {
8304e438cbSEd Tanous 
8404e438cbSEd Tanous #ifndef BMCWEB_INSECURE_DISABLE_CSRF_PREVENTION
85a90daf18SEd Tanous             if (session != nullptr)
86a90daf18SEd Tanous             {
8704e438cbSEd Tanous                 // use protocol for csrf checking
8804e438cbSEd Tanous                 if (session->cookieAuth &&
8904e438cbSEd Tanous                     !crow::utility::constantTimeStringCompare(
9004e438cbSEd Tanous                         protocol, session->csrfToken))
9104e438cbSEd Tanous                 {
9204e438cbSEd Tanous                     BMCWEB_LOG_ERROR << "Websocket CSRF error";
9304e438cbSEd Tanous                     m.result(boost::beast::http::status::unauthorized);
9404e438cbSEd Tanous                     return;
9504e438cbSEd Tanous                 }
96a90daf18SEd Tanous             }
9704e438cbSEd Tanous #endif
9804e438cbSEd Tanous             if (!protocol.empty())
9904e438cbSEd Tanous             {
10004e438cbSEd Tanous                 m.insert(bf::sec_websocket_protocol, protocol);
10104e438cbSEd Tanous             }
10204e438cbSEd Tanous 
10304e438cbSEd Tanous             m.insert(bf::strict_transport_security, "max-age=31536000; "
10404e438cbSEd Tanous                                                     "includeSubdomains; "
10504e438cbSEd Tanous                                                     "preload");
10604e438cbSEd Tanous             m.insert(bf::pragma, "no-cache");
10704e438cbSEd Tanous             m.insert(bf::cache_control, "no-Store,no-Cache");
10804e438cbSEd Tanous             m.insert("Content-Security-Policy", "default-src 'self'");
10904e438cbSEd Tanous             m.insert("X-XSS-Protection", "1; "
11004e438cbSEd Tanous                                          "mode=block");
11104e438cbSEd Tanous             m.insert("X-Content-Type-Options", "nosniff");
11204e438cbSEd Tanous         }));
11304e438cbSEd Tanous 
11404e438cbSEd Tanous         // Perform the websocket upgrade
11504e438cbSEd Tanous         ws.async_accept(req, [this, self(shared_from_this())](
1165e7e2dc5SEd Tanous                                  const boost::system::error_code& ec) {
11704e438cbSEd Tanous             if (ec)
11804e438cbSEd Tanous             {
11904e438cbSEd Tanous                 BMCWEB_LOG_ERROR << "Error in ws.async_accept " << ec;
12004e438cbSEd Tanous                 return;
12104e438cbSEd Tanous             }
12204e438cbSEd Tanous             acceptDone();
12304e438cbSEd Tanous         });
12404e438cbSEd Tanous     }
12504e438cbSEd Tanous 
12626ccae32SEd Tanous     void sendBinary(std::string_view msg) override
12704e438cbSEd Tanous     {
12804e438cbSEd Tanous         ws.binary(true);
12904e438cbSEd Tanous         outBuffer.emplace_back(msg);
13004e438cbSEd Tanous         doWrite();
13104e438cbSEd Tanous     }
13204e438cbSEd Tanous 
13304e438cbSEd Tanous     void sendBinary(std::string&& msg) override
13404e438cbSEd Tanous     {
13504e438cbSEd Tanous         ws.binary(true);
13604e438cbSEd Tanous         outBuffer.emplace_back(std::move(msg));
13704e438cbSEd Tanous         doWrite();
13804e438cbSEd Tanous     }
13904e438cbSEd Tanous 
14026ccae32SEd Tanous     void sendText(std::string_view msg) override
14104e438cbSEd Tanous     {
14204e438cbSEd Tanous         ws.text(true);
14304e438cbSEd Tanous         outBuffer.emplace_back(msg);
14404e438cbSEd Tanous         doWrite();
14504e438cbSEd Tanous     }
14604e438cbSEd Tanous 
14704e438cbSEd Tanous     void sendText(std::string&& msg) override
14804e438cbSEd Tanous     {
14904e438cbSEd Tanous         ws.text(true);
15004e438cbSEd Tanous         outBuffer.emplace_back(std::move(msg));
15104e438cbSEd Tanous         doWrite();
15204e438cbSEd Tanous     }
15304e438cbSEd Tanous 
15426ccae32SEd Tanous     void close(std::string_view msg) override
15504e438cbSEd Tanous     {
15604e438cbSEd Tanous         ws.async_close(
15704e438cbSEd Tanous             {boost::beast::websocket::close_code::normal, msg},
1585e7e2dc5SEd Tanous             [self(shared_from_this())](const boost::system::error_code& ec) {
15904e438cbSEd Tanous             if (ec == boost::asio::error::operation_aborted)
16004e438cbSEd Tanous             {
16104e438cbSEd Tanous                 return;
16204e438cbSEd Tanous             }
16304e438cbSEd Tanous             if (ec)
16404e438cbSEd Tanous             {
16504e438cbSEd Tanous                 BMCWEB_LOG_ERROR << "Error closing websocket " << ec;
16604e438cbSEd Tanous                 return;
16704e438cbSEd Tanous             }
16804e438cbSEd Tanous             });
16904e438cbSEd Tanous     }
17004e438cbSEd Tanous 
17104e438cbSEd Tanous     void acceptDone()
17204e438cbSEd Tanous     {
17304e438cbSEd Tanous         BMCWEB_LOG_DEBUG << "Websocket accepted connection";
17404e438cbSEd Tanous 
17572374eb7SNan Zhou         doRead();
17604e438cbSEd Tanous 
17704e438cbSEd Tanous         if (openHandler)
17804e438cbSEd Tanous         {
1797772638eSzhanghch05             openHandler(*this);
18004e438cbSEd Tanous         }
18104e438cbSEd Tanous     }
18204e438cbSEd Tanous 
18304e438cbSEd Tanous     void doRead()
18404e438cbSEd Tanous     {
18504e438cbSEd Tanous         ws.async_read(inBuffer,
18604e438cbSEd Tanous                       [this, self(shared_from_this())](
18781ce609eSEd Tanous                           boost::beast::error_code ec, std::size_t bytesRead) {
18804e438cbSEd Tanous             if (ec)
18904e438cbSEd Tanous             {
19004e438cbSEd Tanous                 if (ec != boost::beast::websocket::error::closed)
19104e438cbSEd Tanous                 {
19204e438cbSEd Tanous                     BMCWEB_LOG_ERROR << "doRead error " << ec;
19304e438cbSEd Tanous                 }
19404e438cbSEd Tanous                 if (closeHandler)
19504e438cbSEd Tanous                 {
196079360aeSEd Tanous                     std::string reason{ws.reason().reason.c_str()};
197079360aeSEd Tanous                     closeHandler(*this, reason);
19804e438cbSEd Tanous                 }
19904e438cbSEd Tanous                 return;
20004e438cbSEd Tanous             }
20104e438cbSEd Tanous             if (messageHandler)
20204e438cbSEd Tanous             {
20304e438cbSEd Tanous                 messageHandler(*this, inString, ws.got_text());
20404e438cbSEd Tanous             }
20581ce609eSEd Tanous             inBuffer.consume(bytesRead);
20604e438cbSEd Tanous             inString.clear();
20704e438cbSEd Tanous             doRead();
20804e438cbSEd Tanous         });
20904e438cbSEd Tanous     }
21004e438cbSEd Tanous 
21104e438cbSEd Tanous     void doWrite()
21204e438cbSEd Tanous     {
21304e438cbSEd Tanous         // If we're already doing a write, ignore the request, it will be picked
21404e438cbSEd Tanous         // up when the current write is complete
21504e438cbSEd Tanous         if (doingWrite)
21604e438cbSEd Tanous         {
21704e438cbSEd Tanous             return;
21804e438cbSEd Tanous         }
21904e438cbSEd Tanous 
22004e438cbSEd Tanous         if (outBuffer.empty())
22104e438cbSEd Tanous         {
22204e438cbSEd Tanous             // Done for now
22304e438cbSEd Tanous             return;
22404e438cbSEd Tanous         }
22504e438cbSEd Tanous         doingWrite = true;
22604e438cbSEd Tanous         ws.async_write(boost::asio::buffer(outBuffer.front()),
22704e438cbSEd Tanous                        [this, self(shared_from_this())](
22804e438cbSEd Tanous                            boost::beast::error_code ec, std::size_t) {
22904e438cbSEd Tanous             doingWrite = false;
23004e438cbSEd Tanous             outBuffer.erase(outBuffer.begin());
23104e438cbSEd Tanous             if (ec == boost::beast::websocket::error::closed)
23204e438cbSEd Tanous             {
23304e438cbSEd Tanous                 // Do nothing here.  doRead handler will call the
23404e438cbSEd Tanous                 // closeHandler.
23504e438cbSEd Tanous                 close("Write error");
23604e438cbSEd Tanous                 return;
23704e438cbSEd Tanous             }
23804e438cbSEd Tanous             if (ec)
23904e438cbSEd Tanous             {
240002d39b4SEd Tanous                 BMCWEB_LOG_ERROR << "Error in ws.async_write " << ec;
24104e438cbSEd Tanous                 return;
24204e438cbSEd Tanous             }
24304e438cbSEd Tanous             doWrite();
24404e438cbSEd Tanous         });
24504e438cbSEd Tanous     }
24604e438cbSEd Tanous 
24704e438cbSEd Tanous   private:
2482aee6ca2SEd Tanous     boost::beast::websocket::stream<Adaptor, false> ws;
24904e438cbSEd Tanous 
25004e438cbSEd Tanous     std::string inString;
25104e438cbSEd Tanous     boost::asio::dynamic_string_buffer<std::string::value_type,
25204e438cbSEd Tanous                                        std::string::traits_type,
25304e438cbSEd Tanous                                        std::string::allocator_type>
25404e438cbSEd Tanous         inBuffer;
25504e438cbSEd Tanous     std::vector<std::string> outBuffer;
25604e438cbSEd Tanous     bool doingWrite = false;
25704e438cbSEd Tanous 
2587772638eSzhanghch05     std::function<void(Connection&)> openHandler;
25904e438cbSEd Tanous     std::function<void(Connection&, const std::string&, bool)> messageHandler;
26004e438cbSEd Tanous     std::function<void(Connection&, const std::string&)> closeHandler;
26104e438cbSEd Tanous     std::function<void(Connection&)> errorHandler;
26204e438cbSEd Tanous     std::shared_ptr<persistent_data::UserSession> session;
26304e438cbSEd Tanous };
26404e438cbSEd Tanous } // namespace websocket
26504e438cbSEd Tanous } // namespace crow
266