xref: /openbmc/bmcweb/http/websocket.hpp (revision 1873a04f43bb414408d1da9a5a775c05474603d2)
104e438cbSEd Tanous #pragma once
23ccb3adbSEd Tanous #include "async_resp.hpp"
3b2896149SEd Tanous #include "http_body.hpp"
404e438cbSEd Tanous #include "http_request.hpp"
504e438cbSEd Tanous 
604e438cbSEd Tanous #include <boost/asio/buffer.hpp>
7863c1c2eSEd Tanous #include <boost/beast/core/multi_buffer.hpp>
804e438cbSEd Tanous #include <boost/beast/websocket.hpp>
904e438cbSEd Tanous 
1004e438cbSEd Tanous #include <array>
1104e438cbSEd Tanous #include <functional>
1204e438cbSEd Tanous 
1304e438cbSEd Tanous #ifdef BMCWEB_ENABLE_SSL
1404e438cbSEd Tanous #include <boost/beast/websocket/ssl.hpp>
1504e438cbSEd Tanous #endif
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;
3904e438cbSEd Tanous     virtual void sendBinary(std::string&& msg) = 0;
40863c1c2eSEd Tanous     virtual void sendEx(MessageType type, std::string_view msg,
41863c1c2eSEd 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;
45863c1c2eSEd Tanous     virtual void deferRead() = 0;
46863c1c2eSEd Tanous     virtual void resumeRead() = 0;
4704e438cbSEd Tanous     virtual boost::asio::io_context& getIoContext() = 0;
4804e438cbSEd Tanous     virtual ~Connection() = default;
49052bcbf4SNinad Palsule     virtual boost::urls::url_view url() = 0;
5004e438cbSEd Tanous };
5104e438cbSEd Tanous 
5204e438cbSEd Tanous template <typename Adaptor>
5304e438cbSEd Tanous class ConnectionImpl : public Connection
5404e438cbSEd Tanous {
555ebb9d33SEd Tanous     using self_t = ConnectionImpl<Adaptor>;
565ebb9d33SEd Tanous 
5704e438cbSEd Tanous   public:
5804e438cbSEd Tanous     ConnectionImpl(
595ebb9d33SEd Tanous         const boost::urls::url_view& urlViewIn,
605ebb9d33SEd Tanous         const std::shared_ptr<persistent_data::UserSession>& sessionIn,
61052bcbf4SNinad Palsule         Adaptor adaptorIn, std::function<void(Connection&)> openHandlerIn,
6204e438cbSEd Tanous         std::function<void(Connection&, const std::string&, bool)>
638a592810SEd Tanous             messageHandlerIn,
64863c1c2eSEd Tanous         std::function<void(crow::websocket::Connection&, std::string_view,
65863c1c2eSEd Tanous                            crow::websocket::MessageType type,
66863c1c2eSEd Tanous                            std::function<void()>&& whenComplete)>
67863c1c2eSEd Tanous             messageExHandlerIn,
688a592810SEd Tanous         std::function<void(Connection&, const std::string&)> closeHandlerIn,
698a592810SEd Tanous         std::function<void(Connection&)> errorHandlerIn) :
705ebb9d33SEd Tanous         uri(urlViewIn),
715ebb9d33SEd Tanous         ws(std::move(adaptorIn)), inBuffer(inString, 131088),
728a592810SEd Tanous         openHandler(std::move(openHandlerIn)),
738a592810SEd Tanous         messageHandler(std::move(messageHandlerIn)),
74863c1c2eSEd Tanous         messageExHandler(std::move(messageExHandlerIn)),
758a592810SEd Tanous         closeHandler(std::move(closeHandlerIn)),
765ebb9d33SEd Tanous         errorHandler(std::move(errorHandlerIn)), session(sessionIn)
7704e438cbSEd Tanous     {
7802bdd967Sdhineskumare         /* Turn on the timeouts on websocket stream to server role */
7902bdd967Sdhineskumare         ws.set_option(boost::beast::websocket::stream_base::timeout::suggested(
8002bdd967Sdhineskumare             boost::beast::role_type::server));
8162598e31SEd Tanous         BMCWEB_LOG_DEBUG("Creating new connection {}", logPtr(this));
8204e438cbSEd Tanous     }
8304e438cbSEd Tanous 
8404e438cbSEd Tanous     boost::asio::io_context& getIoContext() override
8504e438cbSEd Tanous     {
8604e438cbSEd Tanous         return static_cast<boost::asio::io_context&>(
8704e438cbSEd Tanous             ws.get_executor().context());
8804e438cbSEd Tanous     }
8904e438cbSEd Tanous 
905ebb9d33SEd Tanous     void start(const crow::Request& req)
9104e438cbSEd Tanous     {
9262598e31SEd Tanous         BMCWEB_LOG_DEBUG("starting connection {}", logPtr(this));
9304e438cbSEd Tanous 
9404e438cbSEd Tanous         using bf = boost::beast::http::field;
95*1873a04fSMyung Bae         std::string protocolHeader{
96*1873a04fSMyung Bae             req.getHeaderValue(bf::sec_websocket_protocol)};
9704e438cbSEd Tanous 
9804e438cbSEd Tanous         ws.set_option(boost::beast::websocket::stream_base::decorator(
995ebb9d33SEd Tanous             [session{session},
1005ebb9d33SEd Tanous              protocolHeader](boost::beast::websocket::response_type& m) {
10104e438cbSEd Tanous 
10204e438cbSEd Tanous #ifndef BMCWEB_INSECURE_DISABLE_CSRF_PREVENTION
103a90daf18SEd Tanous             if (session != nullptr)
104a90daf18SEd Tanous             {
10504e438cbSEd Tanous                 // use protocol for csrf checking
1067e9c08edSEd Tanous                 if (session->cookieAuth &&
1077e9c08edSEd Tanous                     !crow::utility::constantTimeStringCompare(
1085ebb9d33SEd Tanous                         protocolHeader, session->csrfToken))
10904e438cbSEd Tanous                 {
11062598e31SEd Tanous                     BMCWEB_LOG_ERROR("Websocket CSRF error");
11104e438cbSEd Tanous                     m.result(boost::beast::http::status::unauthorized);
11204e438cbSEd Tanous                     return;
11304e438cbSEd Tanous                 }
114a90daf18SEd Tanous             }
11504e438cbSEd Tanous #endif
1165ebb9d33SEd Tanous             if (!protocolHeader.empty())
11704e438cbSEd Tanous             {
1185ebb9d33SEd Tanous                 m.insert(bf::sec_websocket_protocol, protocolHeader);
11904e438cbSEd Tanous             }
12004e438cbSEd Tanous 
12104e438cbSEd Tanous             m.insert(bf::strict_transport_security, "max-age=31536000; "
12204e438cbSEd Tanous                                                     "includeSubdomains; "
12304e438cbSEd Tanous                                                     "preload");
12404e438cbSEd Tanous             m.insert(bf::pragma, "no-cache");
12504e438cbSEd Tanous             m.insert(bf::cache_control, "no-Store,no-Cache");
12604e438cbSEd Tanous             m.insert("Content-Security-Policy", "default-src 'self'");
12704e438cbSEd Tanous             m.insert("X-XSS-Protection", "1; "
12804e438cbSEd Tanous                                          "mode=block");
12904e438cbSEd Tanous             m.insert("X-Content-Type-Options", "nosniff");
13004e438cbSEd Tanous         }));
13104e438cbSEd Tanous 
1325ebb9d33SEd Tanous         // Make a pointer to keep the req alive while we accept it.
133b2896149SEd Tanous         using Body = boost::beast::http::request<bmcweb::HttpBody>;
1345ebb9d33SEd Tanous         std::unique_ptr<Body> mobile = std::make_unique<Body>(req.req);
1355ebb9d33SEd Tanous         Body* ptr = mobile.get();
13604e438cbSEd Tanous         // Perform the websocket upgrade
1375ebb9d33SEd Tanous         ws.async_accept(*ptr,
1385ebb9d33SEd Tanous                         std::bind_front(&self_t::acceptDone, this,
1395ebb9d33SEd Tanous                                         shared_from_this(), std::move(mobile)));
14004e438cbSEd Tanous     }
14104e438cbSEd Tanous 
14226ccae32SEd Tanous     void sendBinary(std::string_view msg) override
14304e438cbSEd Tanous     {
14404e438cbSEd Tanous         ws.binary(true);
145863c1c2eSEd Tanous         outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()),
146863c1c2eSEd Tanous                                                   boost::asio::buffer(msg)));
14704e438cbSEd Tanous         doWrite();
14804e438cbSEd Tanous     }
14904e438cbSEd Tanous 
150863c1c2eSEd Tanous     void sendEx(MessageType type, std::string_view msg,
151863c1c2eSEd Tanous                 std::function<void()>&& onDone) override
152863c1c2eSEd Tanous     {
153863c1c2eSEd Tanous         if (doingWrite)
154863c1c2eSEd Tanous         {
15562598e31SEd Tanous             BMCWEB_LOG_CRITICAL(
15662598e31SEd Tanous                 "Cannot mix sendEx usage with sendBinary or sendText");
157863c1c2eSEd Tanous             onDone();
158863c1c2eSEd Tanous             return;
159863c1c2eSEd Tanous         }
160863c1c2eSEd Tanous         ws.binary(type == MessageType::Binary);
161863c1c2eSEd Tanous 
162863c1c2eSEd Tanous         ws.async_write(boost::asio::buffer(msg),
163863c1c2eSEd Tanous                        [weak(weak_from_this()), onDone{std::move(onDone)}](
164863c1c2eSEd Tanous                            const boost::beast::error_code& ec, size_t) {
165863c1c2eSEd Tanous             std::shared_ptr<Connection> self = weak.lock();
166a8894201Szhaogang.0108             if (!self)
167a8894201Szhaogang.0108             {
168a8894201Szhaogang.0108                 BMCWEB_LOG_ERROR("Connection went away");
169a8894201Szhaogang.0108                 return;
170a8894201Szhaogang.0108             }
171863c1c2eSEd Tanous 
172863c1c2eSEd Tanous             // Call the done handler regardless of whether we
173863c1c2eSEd Tanous             // errored, but before we close things out
174863c1c2eSEd Tanous             onDone();
175863c1c2eSEd Tanous 
176863c1c2eSEd Tanous             if (ec)
177863c1c2eSEd Tanous             {
17862598e31SEd Tanous                 BMCWEB_LOG_ERROR("Error in ws.async_write {}", ec);
179863c1c2eSEd Tanous                 self->close("write error");
180863c1c2eSEd Tanous             }
181863c1c2eSEd Tanous         });
182863c1c2eSEd Tanous     }
183863c1c2eSEd Tanous 
18404e438cbSEd Tanous     void sendBinary(std::string&& msg) override
18504e438cbSEd Tanous     {
18604e438cbSEd Tanous         ws.binary(true);
187863c1c2eSEd Tanous         outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()),
188863c1c2eSEd Tanous                                                   boost::asio::buffer(msg)));
18904e438cbSEd Tanous         doWrite();
19004e438cbSEd Tanous     }
19104e438cbSEd Tanous 
19226ccae32SEd Tanous     void sendText(std::string_view msg) override
19304e438cbSEd Tanous     {
19404e438cbSEd Tanous         ws.text(true);
195863c1c2eSEd Tanous         outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()),
196863c1c2eSEd Tanous                                                   boost::asio::buffer(msg)));
19704e438cbSEd Tanous         doWrite();
19804e438cbSEd Tanous     }
19904e438cbSEd Tanous 
20004e438cbSEd Tanous     void sendText(std::string&& msg) override
20104e438cbSEd Tanous     {
20204e438cbSEd Tanous         ws.text(true);
203863c1c2eSEd Tanous         outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()),
204863c1c2eSEd Tanous                                                   boost::asio::buffer(msg)));
20504e438cbSEd Tanous         doWrite();
20604e438cbSEd Tanous     }
20704e438cbSEd Tanous 
20826ccae32SEd Tanous     void close(std::string_view msg) override
20904e438cbSEd Tanous     {
21004e438cbSEd Tanous         ws.async_close(
21104e438cbSEd Tanous             {boost::beast::websocket::close_code::normal, msg},
2125e7e2dc5SEd Tanous             [self(shared_from_this())](const boost::system::error_code& ec) {
21304e438cbSEd Tanous             if (ec == boost::asio::error::operation_aborted)
21404e438cbSEd Tanous             {
21504e438cbSEd Tanous                 return;
21604e438cbSEd Tanous             }
21704e438cbSEd Tanous             if (ec)
21804e438cbSEd Tanous             {
21962598e31SEd Tanous                 BMCWEB_LOG_ERROR("Error closing websocket {}", ec);
22004e438cbSEd Tanous                 return;
22104e438cbSEd Tanous             }
22204e438cbSEd Tanous         });
22304e438cbSEd Tanous     }
22404e438cbSEd Tanous 
225052bcbf4SNinad Palsule     boost::urls::url_view url() override
226052bcbf4SNinad Palsule     {
227052bcbf4SNinad Palsule         return uri;
228052bcbf4SNinad Palsule     }
229052bcbf4SNinad Palsule 
2305ebb9d33SEd Tanous     void acceptDone(const std::shared_ptr<Connection>& /*self*/,
23152e31629SEd Tanous                     const std::unique_ptr<
232b2896149SEd Tanous                         boost::beast::http::request<bmcweb::HttpBody>>& /*req*/,
2335ebb9d33SEd Tanous                     const boost::system::error_code& ec)
23404e438cbSEd Tanous     {
2355ebb9d33SEd Tanous         if (ec)
2365ebb9d33SEd Tanous         {
2375ebb9d33SEd Tanous             BMCWEB_LOG_ERROR("Error in ws.async_accept {}", ec);
2385ebb9d33SEd Tanous             return;
2395ebb9d33SEd Tanous         }
24062598e31SEd Tanous         BMCWEB_LOG_DEBUG("Websocket accepted connection");
24104e438cbSEd Tanous 
24204e438cbSEd Tanous         if (openHandler)
24304e438cbSEd Tanous         {
2447772638eSzhanghch05             openHandler(*this);
24504e438cbSEd Tanous         }
246863c1c2eSEd Tanous         doRead();
247863c1c2eSEd Tanous     }
248863c1c2eSEd Tanous 
249863c1c2eSEd Tanous     void deferRead() override
250863c1c2eSEd Tanous     {
251863c1c2eSEd Tanous         readingDefered = true;
252863c1c2eSEd Tanous 
253863c1c2eSEd Tanous         // If we're not actively reading, we need to take ownership of
254863c1c2eSEd Tanous         // ourselves for a small portion of time, do that, and clear when we
255863c1c2eSEd Tanous         // resume.
256863c1c2eSEd Tanous         selfOwned = shared_from_this();
257863c1c2eSEd Tanous     }
258863c1c2eSEd Tanous 
259863c1c2eSEd Tanous     void resumeRead() override
260863c1c2eSEd Tanous     {
261863c1c2eSEd Tanous         readingDefered = false;
262863c1c2eSEd Tanous         doRead();
263863c1c2eSEd Tanous 
264863c1c2eSEd Tanous         // No longer need to keep ourselves alive now that read is active.
265863c1c2eSEd Tanous         selfOwned.reset();
26604e438cbSEd Tanous     }
26704e438cbSEd Tanous 
26804e438cbSEd Tanous     void doRead()
26904e438cbSEd Tanous     {
270863c1c2eSEd Tanous         if (readingDefered)
271863c1c2eSEd Tanous         {
272863c1c2eSEd Tanous             return;
273863c1c2eSEd Tanous         }
274863c1c2eSEd Tanous         ws.async_read(inBuffer, [this, self(shared_from_this())](
275863c1c2eSEd Tanous                                     const boost::beast::error_code& ec,
276863c1c2eSEd Tanous                                     size_t bytesRead) {
27704e438cbSEd Tanous             if (ec)
27804e438cbSEd Tanous             {
27904e438cbSEd Tanous                 if (ec != boost::beast::websocket::error::closed)
28004e438cbSEd Tanous                 {
28162598e31SEd Tanous                     BMCWEB_LOG_ERROR("doRead error {}", ec);
28204e438cbSEd Tanous                 }
28304e438cbSEd Tanous                 if (closeHandler)
28404e438cbSEd Tanous                 {
285079360aeSEd Tanous                     std::string reason{ws.reason().reason.c_str()};
286079360aeSEd Tanous                     closeHandler(*this, reason);
28704e438cbSEd Tanous                 }
28804e438cbSEd Tanous                 return;
28904e438cbSEd Tanous             }
290863c1c2eSEd Tanous 
291863c1c2eSEd Tanous             handleMessage(bytesRead);
29204e438cbSEd Tanous         });
29304e438cbSEd Tanous     }
29404e438cbSEd Tanous     void doWrite()
29504e438cbSEd Tanous     {
29604e438cbSEd Tanous         // If we're already doing a write, ignore the request, it will be picked
29704e438cbSEd Tanous         // up when the current write is complete
29804e438cbSEd Tanous         if (doingWrite)
29904e438cbSEd Tanous         {
30004e438cbSEd Tanous             return;
30104e438cbSEd Tanous         }
30204e438cbSEd Tanous 
303863c1c2eSEd Tanous         if (outBuffer.size() == 0)
30404e438cbSEd Tanous         {
30504e438cbSEd Tanous             // Done for now
30604e438cbSEd Tanous             return;
30704e438cbSEd Tanous         }
30804e438cbSEd Tanous         doingWrite = true;
309863c1c2eSEd Tanous         ws.async_write(outBuffer.data(), [this, self(shared_from_this())](
310863c1c2eSEd Tanous                                              const boost::beast::error_code& ec,
311863c1c2eSEd Tanous                                              size_t bytesSent) {
31204e438cbSEd Tanous             doingWrite = false;
313863c1c2eSEd Tanous             outBuffer.consume(bytesSent);
31404e438cbSEd Tanous             if (ec == boost::beast::websocket::error::closed)
31504e438cbSEd Tanous             {
31604e438cbSEd Tanous                 // Do nothing here.  doRead handler will call the
31704e438cbSEd Tanous                 // closeHandler.
31804e438cbSEd Tanous                 close("Write error");
31904e438cbSEd Tanous                 return;
32004e438cbSEd Tanous             }
32104e438cbSEd Tanous             if (ec)
32204e438cbSEd Tanous             {
32362598e31SEd Tanous                 BMCWEB_LOG_ERROR("Error in ws.async_write {}", ec);
32404e438cbSEd Tanous                 return;
32504e438cbSEd Tanous             }
32604e438cbSEd Tanous             doWrite();
32704e438cbSEd Tanous         });
32804e438cbSEd Tanous     }
32904e438cbSEd Tanous 
33004e438cbSEd Tanous   private:
331863c1c2eSEd Tanous     void handleMessage(size_t bytesRead)
332863c1c2eSEd Tanous     {
333863c1c2eSEd Tanous         if (messageExHandler)
334863c1c2eSEd Tanous         {
335863c1c2eSEd Tanous             // Note, because of the interactions with the read buffers,
336863c1c2eSEd Tanous             // this message handler overrides the normal message handler
337863c1c2eSEd Tanous             messageExHandler(*this, inString, MessageType::Binary,
338863c1c2eSEd Tanous                              [this, self(shared_from_this()), bytesRead]() {
339863c1c2eSEd Tanous                 if (self == nullptr)
340863c1c2eSEd Tanous                 {
341863c1c2eSEd Tanous                     return;
342863c1c2eSEd Tanous                 }
343863c1c2eSEd Tanous 
344863c1c2eSEd Tanous                 inBuffer.consume(bytesRead);
345863c1c2eSEd Tanous                 inString.clear();
346863c1c2eSEd Tanous 
347863c1c2eSEd Tanous                 doRead();
348863c1c2eSEd Tanous             });
349863c1c2eSEd Tanous             return;
350863c1c2eSEd Tanous         }
351863c1c2eSEd Tanous 
352863c1c2eSEd Tanous         if (messageHandler)
353863c1c2eSEd Tanous         {
354863c1c2eSEd Tanous             messageHandler(*this, inString, ws.got_text());
355863c1c2eSEd Tanous         }
356863c1c2eSEd Tanous         inBuffer.consume(bytesRead);
357863c1c2eSEd Tanous         inString.clear();
358863c1c2eSEd Tanous         doRead();
359863c1c2eSEd Tanous     }
360863c1c2eSEd Tanous 
361052bcbf4SNinad Palsule     boost::urls::url uri;
362052bcbf4SNinad Palsule 
3632aee6ca2SEd Tanous     boost::beast::websocket::stream<Adaptor, false> ws;
36404e438cbSEd Tanous 
365863c1c2eSEd Tanous     bool readingDefered = false;
36604e438cbSEd Tanous     std::string inString;
36704e438cbSEd Tanous     boost::asio::dynamic_string_buffer<std::string::value_type,
36804e438cbSEd Tanous                                        std::string::traits_type,
36904e438cbSEd Tanous                                        std::string::allocator_type>
37004e438cbSEd Tanous         inBuffer;
371863c1c2eSEd Tanous 
372863c1c2eSEd Tanous     boost::beast::multi_buffer outBuffer;
37304e438cbSEd Tanous     bool doingWrite = false;
37404e438cbSEd Tanous 
3757772638eSzhanghch05     std::function<void(Connection&)> openHandler;
37604e438cbSEd Tanous     std::function<void(Connection&, const std::string&, bool)> messageHandler;
377863c1c2eSEd Tanous     std::function<void(crow::websocket::Connection&, std::string_view,
378863c1c2eSEd Tanous                        crow::websocket::MessageType type,
379863c1c2eSEd Tanous                        std::function<void()>&& whenComplete)>
380863c1c2eSEd Tanous         messageExHandler;
38104e438cbSEd Tanous     std::function<void(Connection&, const std::string&)> closeHandler;
38204e438cbSEd Tanous     std::function<void(Connection&)> errorHandler;
38304e438cbSEd Tanous     std::shared_ptr<persistent_data::UserSession> session;
384863c1c2eSEd Tanous 
385863c1c2eSEd Tanous     std::shared_ptr<Connection> selfOwned;
38604e438cbSEd Tanous };
38704e438cbSEd Tanous } // namespace websocket
38804e438cbSEd Tanous } // namespace crow
389