blob: 216e96fef65bbc1b050707b4731d0fa56fccc66c [file] [log] [blame]
Ed Tanous7045c8d2017-04-03 10:04:37 -07001#pragma once
Ed Tanous3ccb3ad2023-01-13 17:40:03 -08002#include "async_resp.hpp"
Ed Tanous04e438c2020-10-03 08:06:26 -07003#include "http_request.hpp"
Gunnar Mills1214b7e2020-06-04 10:11:30 -05004
Ed Tanous609145a2018-09-05 16:27:36 -07005#include <boost/asio/buffer.hpp>
Ed Tanous1b0044b2018-08-03 14:30:05 -07006#include <boost/beast/websocket.hpp>
Ed Tanous1abe55e2018-09-05 08:30:59 -07007
Gunnar Mills1214b7e2020-06-04 10:11:30 -05008#include <array>
9#include <functional>
Ed Tanous1b0044b2018-08-03 14:30:05 -070010
11#ifdef BMCWEB_ENABLE_SSL
12#include <boost/beast/websocket/ssl.hpp>
13#endif
Ed Tanous7045c8d2017-04-03 10:04:37 -070014
Ed Tanous1abe55e2018-09-05 08:30:59 -070015namespace crow
16{
17namespace websocket
18{
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020019
Ed Tanous1abe55e2018-09-05 08:30:59 -070020struct Connection : std::enable_shared_from_this<Connection>
21{
22 public:
Ed Tanouse551b5f2023-02-27 14:19:07 -080023 explicit Connection(const crow::Request& reqIn) : req(reqIn.req)
Ed Tanous23a21a12020-07-25 04:45:05 +000024 {}
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +010025
Ed Tanousecd6a3a2022-01-07 09:18:40 -080026 Connection(const Connection&) = delete;
27 Connection(Connection&&) = delete;
28 Connection& operator=(const Connection&) = delete;
29 Connection& operator=(const Connection&&) = delete;
30
Ed Tanous9eb808c2022-01-25 10:19:23 -080031 virtual void sendBinary(std::string_view msg) = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070032 virtual void sendBinary(std::string&& msg) = 0;
Ed Tanous9eb808c2022-01-25 10:19:23 -080033 virtual void sendText(std::string_view msg) = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070034 virtual void sendText(std::string&& msg) = 0;
Ed Tanous9eb808c2022-01-25 10:19:23 -080035 virtual void close(std::string_view msg = "quit") = 0;
Ed Tanous2c70f802020-09-28 14:29:23 -070036 virtual boost::asio::io_context& getIoContext() = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070037 virtual ~Connection() = default;
Ed Tanous7045c8d2017-04-03 10:04:37 -070038
Jan Sowinskiee52ae12020-01-09 16:28:32 +000039 boost::beast::http::request<boost::beast::http::string_body> req;
Ed Tanous7045c8d2017-04-03 10:04:37 -070040};
41
Gunnar Mills1214b7e2020-06-04 10:11:30 -050042template <typename Adaptor>
43class ConnectionImpl : public Connection
Ed Tanous1abe55e2018-09-05 08:30:59 -070044{
45 public:
46 ConnectionImpl(
Jan Sowinskiee52ae12020-01-09 16:28:32 +000047 const crow::Request& reqIn, Adaptor adaptorIn,
Ed Tanous8a592812022-06-04 09:06:59 -070048 std::function<void(Connection&)> openHandlerIn,
Ed Tanous1abe55e2018-09-05 08:30:59 -070049 std::function<void(Connection&, const std::string&, bool)>
Ed Tanous8a592812022-06-04 09:06:59 -070050 messageHandlerIn,
51 std::function<void(Connection&, const std::string&)> closeHandlerIn,
52 std::function<void(Connection&)> errorHandlerIn) :
Ed Tanouse551b5f2023-02-27 14:19:07 -080053 Connection(reqIn),
Ed Tanouse05aec52022-01-25 10:28:56 -080054 ws(std::move(adaptorIn)), inBuffer(inString, 131088),
Ed Tanous8a592812022-06-04 09:06:59 -070055 openHandler(std::move(openHandlerIn)),
56 messageHandler(std::move(messageHandlerIn)),
57 closeHandler(std::move(closeHandlerIn)),
58 errorHandler(std::move(errorHandlerIn)), session(reqIn.session)
Ed Tanous1abe55e2018-09-05 08:30:59 -070059 {
dhineskumare02bdd962021-07-08 16:06:49 +053060 /* Turn on the timeouts on websocket stream to server role */
61 ws.set_option(boost::beast::websocket::stream_base::timeout::suggested(
62 boost::beast::role_type::server));
Ed Tanous1abe55e2018-09-05 08:30:59 -070063 BMCWEB_LOG_DEBUG << "Creating new connection " << this;
Ed Tanous7045c8d2017-04-03 10:04:37 -070064 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070065
Ed Tanous2c70f802020-09-28 14:29:23 -070066 boost::asio::io_context& getIoContext() override
Ed Tanous1abe55e2018-09-05 08:30:59 -070067 {
Ed Tanous271584a2019-07-09 16:24:22 -070068 return static_cast<boost::asio::io_context&>(
69 ws.get_executor().context());
Ed Tanous911ac312017-08-15 09:37:42 -070070 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070071
Ed Tanous1abe55e2018-09-05 08:30:59 -070072 void start()
73 {
74 BMCWEB_LOG_DEBUG << "starting connection " << this;
Ed Tanous7045c8d2017-04-03 10:04:37 -070075
Ed Tanousfe5b2162019-05-22 14:28:16 -070076 using bf = boost::beast::http::field;
77
Jan Sowinskiee52ae12020-01-09 16:28:32 +000078 std::string_view protocol = req[bf::sec_websocket_protocol];
Ed Tanous7045c8d2017-04-03 10:04:37 -070079
Ed Tanousd4d77e32020-08-18 00:07:28 -070080 ws.set_option(boost::beast::websocket::stream_base::decorator(
James Feistf8aa3d22020-04-08 18:32:33 -070081 [session{session}, protocol{std::string(protocol)}](
Ed Tanous1abe55e2018-09-05 08:30:59 -070082 boost::beast::websocket::response_type& m) {
James Feistf8aa3d22020-04-08 18:32:33 -070083
84#ifndef BMCWEB_INSECURE_DISABLE_CSRF_PREVENTION
Ed Tanous002d39b2022-05-31 08:59:27 -070085 if (session != nullptr)
86 {
87 // use protocol for csrf checking
88 if (session->cookieAuth &&
89 !crow::utility::constantTimeStringCompare(
90 protocol, session->csrfToken))
James Feistf8aa3d22020-04-08 18:32:33 -070091 {
Ed Tanous002d39b2022-05-31 08:59:27 -070092 BMCWEB_LOG_ERROR << "Websocket CSRF error";
93 m.result(boost::beast::http::status::unauthorized);
94 return;
James Feistf8aa3d22020-04-08 18:32:33 -070095 }
Ed Tanous002d39b2022-05-31 08:59:27 -070096 }
James Feistf8aa3d22020-04-08 18:32:33 -070097#endif
Ed Tanous002d39b2022-05-31 08:59:27 -070098 if (!protocol.empty())
99 {
100 m.insert(bf::sec_websocket_protocol, protocol);
101 }
Ed Tanousfe5b2162019-05-22 14:28:16 -0700102
Ed Tanous002d39b2022-05-31 08:59:27 -0700103 m.insert(bf::strict_transport_security, "max-age=31536000; "
104 "includeSubdomains; "
105 "preload");
106 m.insert(bf::pragma, "no-cache");
107 m.insert(bf::cache_control, "no-Store,no-Cache");
108 m.insert("Content-Security-Policy", "default-src 'self'");
109 m.insert("X-XSS-Protection", "1; "
110 "mode=block");
111 m.insert("X-Content-Type-Options", "nosniff");
112 }));
Ed Tanousd4d77e32020-08-18 00:07:28 -0700113
114 // Perform the websocket upgrade
115 ws.async_accept(req, [this, self(shared_from_this())](
Ed Tanous5e7e2dc2023-02-16 10:37:01 -0800116 const boost::system::error_code& ec) {
Ed Tanousd4d77e32020-08-18 00:07:28 -0700117 if (ec)
118 {
119 BMCWEB_LOG_ERROR << "Error in ws.async_accept " << ec;
120 return;
121 }
122 acceptDone();
123 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700124 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700125
Ed Tanous26ccae32023-02-16 10:28:44 -0800126 void sendBinary(std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700127 {
128 ws.binary(true);
129 outBuffer.emplace_back(msg);
130 doWrite();
131 }
132
133 void sendBinary(std::string&& msg) override
134 {
135 ws.binary(true);
136 outBuffer.emplace_back(std::move(msg));
137 doWrite();
138 }
139
Ed Tanous26ccae32023-02-16 10:28:44 -0800140 void sendText(std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700141 {
142 ws.text(true);
143 outBuffer.emplace_back(msg);
144 doWrite();
145 }
146
147 void sendText(std::string&& msg) override
148 {
149 ws.text(true);
150 outBuffer.emplace_back(std::move(msg));
151 doWrite();
152 }
153
Ed Tanous26ccae32023-02-16 10:28:44 -0800154 void close(std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700155 {
156 ws.async_close(
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200157 {boost::beast::websocket::close_code::normal, msg},
Ed Tanous5e7e2dc2023-02-16 10:37:01 -0800158 [self(shared_from_this())](const boost::system::error_code& ec) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700159 if (ec == boost::asio::error::operation_aborted)
160 {
161 return;
162 }
163 if (ec)
164 {
165 BMCWEB_LOG_ERROR << "Error closing websocket " << ec;
166 return;
167 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700168 });
169 }
170
171 void acceptDone()
172 {
173 BMCWEB_LOG_DEBUG << "Websocket accepted connection";
174
Nan Zhou72374eb2022-01-27 17:06:51 -0800175 doRead();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200176
Ed Tanous1abe55e2018-09-05 08:30:59 -0700177 if (openHandler)
178 {
zhanghch0577726382021-10-21 14:07:57 +0800179 openHandler(*this);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700180 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700181 }
182
183 void doRead()
184 {
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500185 ws.async_read(inBuffer,
186 [this, self(shared_from_this())](
Ed Tanous81ce6092020-12-17 16:54:55 +0000187 boost::beast::error_code ec, std::size_t bytesRead) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700188 if (ec)
189 {
190 if (ec != boost::beast::websocket::error::closed)
191 {
192 BMCWEB_LOG_ERROR << "doRead error " << ec;
193 }
194 if (closeHandler)
195 {
Ed Tanous079360a2022-06-29 10:05:19 -0700196 std::string reason{ws.reason().reason.c_str()};
197 closeHandler(*this, reason);
Ed Tanous002d39b2022-05-31 08:59:27 -0700198 }
199 return;
200 }
201 if (messageHandler)
202 {
203 messageHandler(*this, inString, ws.got_text());
204 }
205 inBuffer.consume(bytesRead);
206 inString.clear();
207 doRead();
208 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700209 }
210
211 void doWrite()
212 {
213 // If we're already doing a write, ignore the request, it will be picked
214 // up when the current write is complete
215 if (doingWrite)
216 {
217 return;
218 }
219
220 if (outBuffer.empty())
221 {
222 // Done for now
223 return;
224 }
225 doingWrite = true;
Ed Tanouscb13a392020-07-25 19:02:03 +0000226 ws.async_write(boost::asio::buffer(outBuffer.front()),
227 [this, self(shared_from_this())](
228 boost::beast::error_code ec, std::size_t) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700229 doingWrite = false;
230 outBuffer.erase(outBuffer.begin());
231 if (ec == boost::beast::websocket::error::closed)
232 {
233 // Do nothing here. doRead handler will call the
234 // closeHandler.
235 close("Write error");
236 return;
237 }
238 if (ec)
239 {
240 BMCWEB_LOG_ERROR << "Error in ws.async_write " << ec;
241 return;
242 }
243 doWrite();
244 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700245 }
246
247 private:
Ed Tanous2aee6ca2021-02-01 09:52:17 -0800248 boost::beast::websocket::stream<Adaptor, false> ws;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700249
Ed Tanous609145a2018-09-05 16:27:36 -0700250 std::string inString;
251 boost::asio::dynamic_string_buffer<std::string::value_type,
252 std::string::traits_type,
253 std::string::allocator_type>
254 inBuffer;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700255 std::vector<std::string> outBuffer;
256 bool doingWrite = false;
257
zhanghch0577726382021-10-21 14:07:57 +0800258 std::function<void(Connection&)> openHandler;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700259 std::function<void(Connection&, const std::string&, bool)> messageHandler;
260 std::function<void(Connection&, const std::string&)> closeHandler;
261 std::function<void(Connection&)> errorHandler;
Ed Tanous52cc1122020-07-18 13:51:21 -0700262 std::shared_ptr<persistent_data::UserSession> session;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700263};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700264} // namespace websocket
265} // namespace crow