blob: 216c4131dbb395e4a23300d184eaa881fb48bd3e [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 Tanous863c1c22022-02-21 21:33:06 -08006#include <boost/beast/core/multi_buffer.hpp>
Ed Tanous1b0044b2018-08-03 14:30:05 -07007#include <boost/beast/websocket.hpp>
Ed Tanous1abe55e2018-09-05 08:30:59 -07008
Gunnar Mills1214b7e2020-06-04 10:11:30 -05009#include <array>
10#include <functional>
Ed Tanous1b0044b2018-08-03 14:30:05 -070011
12#ifdef BMCWEB_ENABLE_SSL
13#include <boost/beast/websocket/ssl.hpp>
14#endif
Ed Tanous7045c8d2017-04-03 10:04:37 -070015
Ed Tanous1abe55e2018-09-05 08:30:59 -070016namespace crow
17{
18namespace websocket
19{
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020020
Ed Tanous863c1c22022-02-21 21:33:06 -080021enum class MessageType
22{
23 Binary,
24 Text,
25};
26
Ed Tanous1abe55e2018-09-05 08:30:59 -070027struct Connection : std::enable_shared_from_this<Connection>
28{
29 public:
Patrick Williams89492a12023-05-10 07:51:34 -050030 explicit Connection(const crow::Request& reqIn) : req(reqIn.req) {}
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +010031
Ed Tanousecd6a3a2022-01-07 09:18:40 -080032 Connection(const Connection&) = delete;
33 Connection(Connection&&) = delete;
34 Connection& operator=(const Connection&) = delete;
35 Connection& operator=(const Connection&&) = delete;
36
Ed Tanous9eb808c2022-01-25 10:19:23 -080037 virtual void sendBinary(std::string_view msg) = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070038 virtual void sendBinary(std::string&& msg) = 0;
Ed Tanous863c1c22022-02-21 21:33:06 -080039 virtual void sendEx(MessageType type, std::string_view msg,
40 std::function<void()>&& onDone) = 0;
Ed Tanous9eb808c2022-01-25 10:19:23 -080041 virtual void sendText(std::string_view msg) = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070042 virtual void sendText(std::string&& msg) = 0;
Ed Tanous9eb808c2022-01-25 10:19:23 -080043 virtual void close(std::string_view msg = "quit") = 0;
Ed Tanous863c1c22022-02-21 21:33:06 -080044 virtual void deferRead() = 0;
45 virtual void resumeRead() = 0;
Ed Tanous2c70f802020-09-28 14:29:23 -070046 virtual boost::asio::io_context& getIoContext() = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070047 virtual ~Connection() = default;
Ninad Palsule052bcbf2023-05-30 11:10:58 -050048 virtual boost::urls::url_view url() = 0;
Jan Sowinskiee52ae12020-01-09 16:28:32 +000049 boost::beast::http::request<boost::beast::http::string_body> req;
Ed Tanous7045c8d2017-04-03 10:04:37 -070050};
51
Gunnar Mills1214b7e2020-06-04 10:11:30 -050052template <typename Adaptor>
53class ConnectionImpl : public Connection
Ed Tanous1abe55e2018-09-05 08:30:59 -070054{
55 public:
56 ConnectionImpl(
Ninad Palsule052bcbf2023-05-30 11:10:58 -050057 const crow::Request& reqIn, boost::urls::url_view urlViewIn,
58 Adaptor adaptorIn, std::function<void(Connection&)> openHandlerIn,
Ed Tanous1abe55e2018-09-05 08:30:59 -070059 std::function<void(Connection&, const std::string&, bool)>
Ed Tanous8a592812022-06-04 09:06:59 -070060 messageHandlerIn,
Ed Tanous863c1c22022-02-21 21:33:06 -080061 std::function<void(crow::websocket::Connection&, std::string_view,
62 crow::websocket::MessageType type,
63 std::function<void()>&& whenComplete)>
64 messageExHandlerIn,
Ed Tanous8a592812022-06-04 09:06:59 -070065 std::function<void(Connection&, const std::string&)> closeHandlerIn,
66 std::function<void(Connection&)> errorHandlerIn) :
Ed Tanouse551b5f2023-02-27 14:19:07 -080067 Connection(reqIn),
Ninad Palsule052bcbf2023-05-30 11:10:58 -050068 uri(urlViewIn), ws(std::move(adaptorIn)), inBuffer(inString, 131088),
Ed Tanous8a592812022-06-04 09:06:59 -070069 openHandler(std::move(openHandlerIn)),
70 messageHandler(std::move(messageHandlerIn)),
Ed Tanous863c1c22022-02-21 21:33:06 -080071 messageExHandler(std::move(messageExHandlerIn)),
Ed Tanous8a592812022-06-04 09:06:59 -070072 closeHandler(std::move(closeHandlerIn)),
73 errorHandler(std::move(errorHandlerIn)), session(reqIn.session)
Ed Tanous1abe55e2018-09-05 08:30:59 -070074 {
dhineskumare02bdd962021-07-08 16:06:49 +053075 /* Turn on the timeouts on websocket stream to server role */
76 ws.set_option(boost::beast::websocket::stream_base::timeout::suggested(
77 boost::beast::role_type::server));
Ed Tanous62598e32023-07-17 17:06:25 -070078 BMCWEB_LOG_DEBUG("Creating new connection {}", logPtr(this));
Ed Tanous7045c8d2017-04-03 10:04:37 -070079 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070080
Ed Tanous2c70f802020-09-28 14:29:23 -070081 boost::asio::io_context& getIoContext() override
Ed Tanous1abe55e2018-09-05 08:30:59 -070082 {
Ed Tanous271584a2019-07-09 16:24:22 -070083 return static_cast<boost::asio::io_context&>(
84 ws.get_executor().context());
Ed Tanous911ac312017-08-15 09:37:42 -070085 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070086
Ed Tanous1abe55e2018-09-05 08:30:59 -070087 void start()
88 {
Ed Tanous62598e32023-07-17 17:06:25 -070089 BMCWEB_LOG_DEBUG("starting connection {}", logPtr(this));
Ed Tanous7045c8d2017-04-03 10:04:37 -070090
Ed Tanousfe5b2162019-05-22 14:28:16 -070091 using bf = boost::beast::http::field;
92
Jan Sowinskiee52ae12020-01-09 16:28:32 +000093 std::string_view protocol = req[bf::sec_websocket_protocol];
Ed Tanous7045c8d2017-04-03 10:04:37 -070094
Ed Tanousd4d77e32020-08-18 00:07:28 -070095 ws.set_option(boost::beast::websocket::stream_base::decorator(
James Feistf8aa3d22020-04-08 18:32:33 -070096 [session{session}, protocol{std::string(protocol)}](
Ed Tanous1abe55e2018-09-05 08:30:59 -070097 boost::beast::websocket::response_type& m) {
James Feistf8aa3d22020-04-08 18:32:33 -070098
99#ifndef BMCWEB_INSECURE_DISABLE_CSRF_PREVENTION
Ed Tanous002d39b2022-05-31 08:59:27 -0700100 if (session != nullptr)
101 {
102 // use protocol for csrf checking
Ed Tanous7e9c08e2023-06-16 11:29:37 -0700103 if (session->cookieAuth &&
104 !crow::utility::constantTimeStringCompare(
Ed Tanous002d39b2022-05-31 08:59:27 -0700105 protocol, session->csrfToken))
James Feistf8aa3d22020-04-08 18:32:33 -0700106 {
Ed Tanous62598e32023-07-17 17:06:25 -0700107 BMCWEB_LOG_ERROR("Websocket CSRF error");
Ed Tanous002d39b2022-05-31 08:59:27 -0700108 m.result(boost::beast::http::status::unauthorized);
109 return;
James Feistf8aa3d22020-04-08 18:32:33 -0700110 }
Ed Tanous002d39b2022-05-31 08:59:27 -0700111 }
James Feistf8aa3d22020-04-08 18:32:33 -0700112#endif
Ed Tanous002d39b2022-05-31 08:59:27 -0700113 if (!protocol.empty())
114 {
115 m.insert(bf::sec_websocket_protocol, protocol);
116 }
Ed Tanousfe5b2162019-05-22 14:28:16 -0700117
Ed Tanous002d39b2022-05-31 08:59:27 -0700118 m.insert(bf::strict_transport_security, "max-age=31536000; "
119 "includeSubdomains; "
120 "preload");
121 m.insert(bf::pragma, "no-cache");
122 m.insert(bf::cache_control, "no-Store,no-Cache");
123 m.insert("Content-Security-Policy", "default-src 'self'");
124 m.insert("X-XSS-Protection", "1; "
125 "mode=block");
126 m.insert("X-Content-Type-Options", "nosniff");
127 }));
Ed Tanousd4d77e32020-08-18 00:07:28 -0700128
129 // Perform the websocket upgrade
130 ws.async_accept(req, [this, self(shared_from_this())](
Ed Tanous5e7e2dc2023-02-16 10:37:01 -0800131 const boost::system::error_code& ec) {
Ed Tanousd4d77e32020-08-18 00:07:28 -0700132 if (ec)
133 {
Ed Tanous62598e32023-07-17 17:06:25 -0700134 BMCWEB_LOG_ERROR("Error in ws.async_accept {}", ec);
Ed Tanousd4d77e32020-08-18 00:07:28 -0700135 return;
136 }
137 acceptDone();
138 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700139 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700140
Ed Tanous26ccae32023-02-16 10:28:44 -0800141 void sendBinary(std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700142 {
143 ws.binary(true);
Ed Tanous863c1c22022-02-21 21:33:06 -0800144 outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()),
145 boost::asio::buffer(msg)));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700146 doWrite();
147 }
148
Ed Tanous863c1c22022-02-21 21:33:06 -0800149 void sendEx(MessageType type, std::string_view msg,
150 std::function<void()>&& onDone) override
151 {
152 if (doingWrite)
153 {
Ed Tanous62598e32023-07-17 17:06:25 -0700154 BMCWEB_LOG_CRITICAL(
155 "Cannot mix sendEx usage with sendBinary or sendText");
Ed Tanous863c1c22022-02-21 21:33:06 -0800156 onDone();
157 return;
158 }
159 ws.binary(type == MessageType::Binary);
160
161 ws.async_write(boost::asio::buffer(msg),
162 [weak(weak_from_this()), onDone{std::move(onDone)}](
163 const boost::beast::error_code& ec, size_t) {
164 std::shared_ptr<Connection> self = weak.lock();
165
166 // Call the done handler regardless of whether we
167 // errored, but before we close things out
168 onDone();
169
170 if (ec)
171 {
Ed Tanous62598e32023-07-17 17:06:25 -0700172 BMCWEB_LOG_ERROR("Error in ws.async_write {}", ec);
Ed Tanous863c1c22022-02-21 21:33:06 -0800173 self->close("write error");
174 }
175 });
176 }
177
Ed Tanous1abe55e2018-09-05 08:30:59 -0700178 void sendBinary(std::string&& msg) override
179 {
180 ws.binary(true);
Ed Tanous863c1c22022-02-21 21:33:06 -0800181 outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()),
182 boost::asio::buffer(msg)));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700183 doWrite();
184 }
185
Ed Tanous26ccae32023-02-16 10:28:44 -0800186 void sendText(std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700187 {
188 ws.text(true);
Ed Tanous863c1c22022-02-21 21:33:06 -0800189 outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()),
190 boost::asio::buffer(msg)));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700191 doWrite();
192 }
193
194 void sendText(std::string&& msg) override
195 {
196 ws.text(true);
Ed Tanous863c1c22022-02-21 21:33:06 -0800197 outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()),
198 boost::asio::buffer(msg)));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700199 doWrite();
200 }
201
Ed Tanous26ccae32023-02-16 10:28:44 -0800202 void close(std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700203 {
204 ws.async_close(
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200205 {boost::beast::websocket::close_code::normal, msg},
Ed Tanous5e7e2dc2023-02-16 10:37:01 -0800206 [self(shared_from_this())](const boost::system::error_code& ec) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700207 if (ec == boost::asio::error::operation_aborted)
208 {
209 return;
210 }
211 if (ec)
212 {
Ed Tanous62598e32023-07-17 17:06:25 -0700213 BMCWEB_LOG_ERROR("Error closing websocket {}", ec);
Ed Tanous002d39b2022-05-31 08:59:27 -0700214 return;
215 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700216 });
217 }
218
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500219 boost::urls::url_view url() override
220 {
221 return uri;
222 }
223
Ed Tanous1abe55e2018-09-05 08:30:59 -0700224 void acceptDone()
225 {
Ed Tanous62598e32023-07-17 17:06:25 -0700226 BMCWEB_LOG_DEBUG("Websocket accepted connection");
Ed Tanous1abe55e2018-09-05 08:30:59 -0700227
228 if (openHandler)
229 {
zhanghch0577726382021-10-21 14:07:57 +0800230 openHandler(*this);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700231 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800232 doRead();
233 }
234
235 void deferRead() override
236 {
237 readingDefered = true;
238
239 // If we're not actively reading, we need to take ownership of
240 // ourselves for a small portion of time, do that, and clear when we
241 // resume.
242 selfOwned = shared_from_this();
243 }
244
245 void resumeRead() override
246 {
247 readingDefered = false;
248 doRead();
249
250 // No longer need to keep ourselves alive now that read is active.
251 selfOwned.reset();
Ed Tanous1abe55e2018-09-05 08:30:59 -0700252 }
253
254 void doRead()
255 {
Ed Tanous863c1c22022-02-21 21:33:06 -0800256 if (readingDefered)
257 {
258 return;
259 }
260 ws.async_read(inBuffer, [this, self(shared_from_this())](
261 const boost::beast::error_code& ec,
262 size_t bytesRead) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700263 if (ec)
264 {
265 if (ec != boost::beast::websocket::error::closed)
266 {
Ed Tanous62598e32023-07-17 17:06:25 -0700267 BMCWEB_LOG_ERROR("doRead error {}", ec);
Ed Tanous002d39b2022-05-31 08:59:27 -0700268 }
269 if (closeHandler)
270 {
Ed Tanous079360a2022-06-29 10:05:19 -0700271 std::string reason{ws.reason().reason.c_str()};
272 closeHandler(*this, reason);
Ed Tanous002d39b2022-05-31 08:59:27 -0700273 }
274 return;
275 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800276
277 handleMessage(bytesRead);
Ed Tanous002d39b2022-05-31 08:59:27 -0700278 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700279 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700280 void doWrite()
281 {
282 // If we're already doing a write, ignore the request, it will be picked
283 // up when the current write is complete
284 if (doingWrite)
285 {
286 return;
287 }
288
Ed Tanous863c1c22022-02-21 21:33:06 -0800289 if (outBuffer.size() == 0)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700290 {
291 // Done for now
292 return;
293 }
294 doingWrite = true;
Ed Tanous863c1c22022-02-21 21:33:06 -0800295 ws.async_write(outBuffer.data(), [this, self(shared_from_this())](
296 const boost::beast::error_code& ec,
297 size_t bytesSent) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700298 doingWrite = false;
Ed Tanous863c1c22022-02-21 21:33:06 -0800299 outBuffer.consume(bytesSent);
Ed Tanous002d39b2022-05-31 08:59:27 -0700300 if (ec == boost::beast::websocket::error::closed)
301 {
302 // Do nothing here. doRead handler will call the
303 // closeHandler.
304 close("Write error");
305 return;
306 }
307 if (ec)
308 {
Ed Tanous62598e32023-07-17 17:06:25 -0700309 BMCWEB_LOG_ERROR("Error in ws.async_write {}", ec);
Ed Tanous002d39b2022-05-31 08:59:27 -0700310 return;
311 }
312 doWrite();
313 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700314 }
315
316 private:
Ed Tanous863c1c22022-02-21 21:33:06 -0800317 void handleMessage(size_t bytesRead)
318 {
319 if (messageExHandler)
320 {
321 // Note, because of the interactions with the read buffers,
322 // this message handler overrides the normal message handler
323 messageExHandler(*this, inString, MessageType::Binary,
324 [this, self(shared_from_this()), bytesRead]() {
325 if (self == nullptr)
326 {
327 return;
328 }
329
330 inBuffer.consume(bytesRead);
331 inString.clear();
332
333 doRead();
334 });
335 return;
336 }
337
338 if (messageHandler)
339 {
340 messageHandler(*this, inString, ws.got_text());
341 }
342 inBuffer.consume(bytesRead);
343 inString.clear();
344 doRead();
345 }
346
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500347 boost::urls::url uri;
348
Ed Tanous2aee6ca2021-02-01 09:52:17 -0800349 boost::beast::websocket::stream<Adaptor, false> ws;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700350
Ed Tanous863c1c22022-02-21 21:33:06 -0800351 bool readingDefered = false;
Ed Tanous609145a2018-09-05 16:27:36 -0700352 std::string inString;
353 boost::asio::dynamic_string_buffer<std::string::value_type,
354 std::string::traits_type,
355 std::string::allocator_type>
356 inBuffer;
Ed Tanous863c1c22022-02-21 21:33:06 -0800357
358 boost::beast::multi_buffer outBuffer;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700359 bool doingWrite = false;
360
zhanghch0577726382021-10-21 14:07:57 +0800361 std::function<void(Connection&)> openHandler;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700362 std::function<void(Connection&, const std::string&, bool)> messageHandler;
Ed Tanous863c1c22022-02-21 21:33:06 -0800363 std::function<void(crow::websocket::Connection&, std::string_view,
364 crow::websocket::MessageType type,
365 std::function<void()>&& whenComplete)>
366 messageExHandler;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700367 std::function<void(Connection&, const std::string&)> closeHandler;
368 std::function<void(Connection&)> errorHandler;
Ed Tanous52cc1122020-07-18 13:51:21 -0700369 std::shared_ptr<persistent_data::UserSession> session;
Ed Tanous863c1c22022-02-21 21:33:06 -0800370
371 std::shared_ptr<Connection> selfOwned;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700372};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700373} // namespace websocket
374} // namespace crow