xref: /openbmc/bmcweb/http/websocket.hpp (revision 863c1c2e78a8cce2e6b5cf0e80b8cf75fbc801b3)
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