blob: 2ef84128868754d0ebc7f237ffe033e5b0357892 [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 Tanousb2896142024-01-31 15:25:47 -08003#include "http_body.hpp"
Ed Tanous04e438c2020-10-03 08:06:26 -07004#include "http_request.hpp"
Gunnar Mills1214b7e2020-06-04 10:11:30 -05005
Ed Tanous609145a2018-09-05 16:27:36 -07006#include <boost/asio/buffer.hpp>
Ed Tanous863c1c22022-02-21 21:33:06 -08007#include <boost/beast/core/multi_buffer.hpp>
Ed Tanous1b0044b2018-08-03 14:30:05 -07008#include <boost/beast/websocket.hpp>
Ed Tanous1abe55e2018-09-05 08:30:59 -07009
Gunnar Mills1214b7e2020-06-04 10:11:30 -050010#include <array>
11#include <functional>
Ed Tanous1b0044b2018-08-03 14:30:05 -070012
13#ifdef BMCWEB_ENABLE_SSL
14#include <boost/beast/websocket/ssl.hpp>
15#endif
Ed Tanous7045c8d2017-04-03 10:04:37 -070016
Ed Tanous1abe55e2018-09-05 08:30:59 -070017namespace crow
18{
19namespace websocket
20{
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020021
Ed Tanous863c1c22022-02-21 21:33:06 -080022enum class MessageType
23{
24 Binary,
25 Text,
26};
27
Ed Tanous1abe55e2018-09-05 08:30:59 -070028struct Connection : std::enable_shared_from_this<Connection>
29{
30 public:
Ed Tanous5ebb9d32023-02-27 18:20:47 -080031 Connection() = default;
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +010032
Ed Tanousecd6a3a2022-01-07 09:18:40 -080033 Connection(const Connection&) = delete;
34 Connection(Connection&&) = delete;
35 Connection& operator=(const Connection&) = delete;
36 Connection& operator=(const Connection&&) = delete;
37
Ed Tanous9eb808c2022-01-25 10:19:23 -080038 virtual void sendBinary(std::string_view msg) = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070039 virtual void sendBinary(std::string&& msg) = 0;
Ed Tanous863c1c22022-02-21 21:33:06 -080040 virtual void sendEx(MessageType type, std::string_view msg,
41 std::function<void()>&& onDone) = 0;
Ed Tanous9eb808c2022-01-25 10:19:23 -080042 virtual void sendText(std::string_view msg) = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070043 virtual void sendText(std::string&& msg) = 0;
Ed Tanous9eb808c2022-01-25 10:19:23 -080044 virtual void close(std::string_view msg = "quit") = 0;
Ed Tanous863c1c22022-02-21 21:33:06 -080045 virtual void deferRead() = 0;
46 virtual void resumeRead() = 0;
Ed Tanous2c70f802020-09-28 14:29:23 -070047 virtual boost::asio::io_context& getIoContext() = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070048 virtual ~Connection() = default;
Ninad Palsule052bcbf2023-05-30 11:10:58 -050049 virtual boost::urls::url_view url() = 0;
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{
Ed Tanous5ebb9d32023-02-27 18:20:47 -080055 using self_t = ConnectionImpl<Adaptor>;
56
Ed Tanous1abe55e2018-09-05 08:30:59 -070057 public:
58 ConnectionImpl(
Ed Tanous5ebb9d32023-02-27 18:20:47 -080059 const boost::urls::url_view& urlViewIn,
60 const std::shared_ptr<persistent_data::UserSession>& sessionIn,
Ninad Palsule052bcbf2023-05-30 11:10:58 -050061 Adaptor adaptorIn, std::function<void(Connection&)> openHandlerIn,
Ed Tanous1abe55e2018-09-05 08:30:59 -070062 std::function<void(Connection&, const std::string&, bool)>
Ed Tanous8a592812022-06-04 09:06:59 -070063 messageHandlerIn,
Ed Tanous863c1c22022-02-21 21:33:06 -080064 std::function<void(crow::websocket::Connection&, std::string_view,
65 crow::websocket::MessageType type,
66 std::function<void()>&& whenComplete)>
67 messageExHandlerIn,
Ed Tanous8a592812022-06-04 09:06:59 -070068 std::function<void(Connection&, const std::string&)> closeHandlerIn,
69 std::function<void(Connection&)> errorHandlerIn) :
Ed Tanous5ebb9d32023-02-27 18:20:47 -080070 uri(urlViewIn),
71 ws(std::move(adaptorIn)), inBuffer(inString, 131088),
Ed Tanous8a592812022-06-04 09:06:59 -070072 openHandler(std::move(openHandlerIn)),
73 messageHandler(std::move(messageHandlerIn)),
Ed Tanous863c1c22022-02-21 21:33:06 -080074 messageExHandler(std::move(messageExHandlerIn)),
Ed Tanous8a592812022-06-04 09:06:59 -070075 closeHandler(std::move(closeHandlerIn)),
Ed Tanous5ebb9d32023-02-27 18:20:47 -080076 errorHandler(std::move(errorHandlerIn)), session(sessionIn)
Ed Tanous1abe55e2018-09-05 08:30:59 -070077 {
dhineskumare02bdd962021-07-08 16:06:49 +053078 /* Turn on the timeouts on websocket stream to server role */
79 ws.set_option(boost::beast::websocket::stream_base::timeout::suggested(
80 boost::beast::role_type::server));
Ed Tanous62598e32023-07-17 17:06:25 -070081 BMCWEB_LOG_DEBUG("Creating new connection {}", logPtr(this));
Ed Tanous7045c8d2017-04-03 10:04:37 -070082 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070083
Ed Tanous2c70f802020-09-28 14:29:23 -070084 boost::asio::io_context& getIoContext() override
Ed Tanous1abe55e2018-09-05 08:30:59 -070085 {
Ed Tanous271584a2019-07-09 16:24:22 -070086 return static_cast<boost::asio::io_context&>(
87 ws.get_executor().context());
Ed Tanous911ac312017-08-15 09:37:42 -070088 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070089
Ed Tanous5ebb9d32023-02-27 18:20:47 -080090 void start(const crow::Request& req)
Ed Tanous1abe55e2018-09-05 08:30:59 -070091 {
Ed Tanous62598e32023-07-17 17:06:25 -070092 BMCWEB_LOG_DEBUG("starting connection {}", logPtr(this));
Ed Tanous7045c8d2017-04-03 10:04:37 -070093
Ed Tanousfe5b2162019-05-22 14:28:16 -070094 using bf = boost::beast::http::field;
Myung Bae1873a042024-04-01 09:27:39 -050095 std::string protocolHeader{
96 req.getHeaderValue(bf::sec_websocket_protocol)};
Ed Tanous7045c8d2017-04-03 10:04:37 -070097
Ed Tanousd4d77e32020-08-18 00:07:28 -070098 ws.set_option(boost::beast::websocket::stream_base::decorator(
Ed Tanous5ebb9d32023-02-27 18:20:47 -080099 [session{session},
100 protocolHeader](boost::beast::websocket::response_type& m) {
James Feistf8aa3d22020-04-08 18:32:33 -0700101
102#ifndef BMCWEB_INSECURE_DISABLE_CSRF_PREVENTION
Ed Tanous002d39b2022-05-31 08:59:27 -0700103 if (session != nullptr)
104 {
105 // use protocol for csrf checking
Ed Tanous7e9c08e2023-06-16 11:29:37 -0700106 if (session->cookieAuth &&
107 !crow::utility::constantTimeStringCompare(
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800108 protocolHeader, session->csrfToken))
James Feistf8aa3d22020-04-08 18:32:33 -0700109 {
Ed Tanous62598e32023-07-17 17:06:25 -0700110 BMCWEB_LOG_ERROR("Websocket CSRF error");
Ed Tanous002d39b2022-05-31 08:59:27 -0700111 m.result(boost::beast::http::status::unauthorized);
112 return;
James Feistf8aa3d22020-04-08 18:32:33 -0700113 }
Ed Tanous002d39b2022-05-31 08:59:27 -0700114 }
James Feistf8aa3d22020-04-08 18:32:33 -0700115#endif
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800116 if (!protocolHeader.empty())
Ed Tanous002d39b2022-05-31 08:59:27 -0700117 {
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800118 m.insert(bf::sec_websocket_protocol, protocolHeader);
Ed Tanous002d39b2022-05-31 08:59:27 -0700119 }
Ed Tanousfe5b2162019-05-22 14:28:16 -0700120
Ed Tanous002d39b2022-05-31 08:59:27 -0700121 m.insert(bf::strict_transport_security, "max-age=31536000; "
122 "includeSubdomains; "
123 "preload");
124 m.insert(bf::pragma, "no-cache");
125 m.insert(bf::cache_control, "no-Store,no-Cache");
126 m.insert("Content-Security-Policy", "default-src 'self'");
127 m.insert("X-XSS-Protection", "1; "
128 "mode=block");
129 m.insert("X-Content-Type-Options", "nosniff");
130 }));
Ed Tanousd4d77e32020-08-18 00:07:28 -0700131
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800132 // Make a pointer to keep the req alive while we accept it.
Ed Tanousb2896142024-01-31 15:25:47 -0800133 using Body = boost::beast::http::request<bmcweb::HttpBody>;
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800134 std::unique_ptr<Body> mobile = std::make_unique<Body>(req.req);
135 Body* ptr = mobile.get();
Ed Tanousd4d77e32020-08-18 00:07:28 -0700136 // Perform the websocket upgrade
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800137 ws.async_accept(*ptr,
138 std::bind_front(&self_t::acceptDone, this,
139 shared_from_this(), std::move(mobile)));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700140 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700141
Ed Tanous26ccae32023-02-16 10:28:44 -0800142 void sendBinary(std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700143 {
144 ws.binary(true);
Ed Tanous863c1c22022-02-21 21:33:06 -0800145 outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()),
146 boost::asio::buffer(msg)));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700147 doWrite();
148 }
149
Ed Tanous863c1c22022-02-21 21:33:06 -0800150 void sendEx(MessageType type, std::string_view msg,
151 std::function<void()>&& onDone) override
152 {
153 if (doingWrite)
154 {
Ed Tanous62598e32023-07-17 17:06:25 -0700155 BMCWEB_LOG_CRITICAL(
156 "Cannot mix sendEx usage with sendBinary or sendText");
Ed Tanous863c1c22022-02-21 21:33:06 -0800157 onDone();
158 return;
159 }
160 ws.binary(type == MessageType::Binary);
161
162 ws.async_write(boost::asio::buffer(msg),
163 [weak(weak_from_this()), onDone{std::move(onDone)}](
164 const boost::beast::error_code& ec, size_t) {
165 std::shared_ptr<Connection> self = weak.lock();
zhaogang.0108a8894202023-12-22 08:53:40 +0000166 if (!self)
167 {
168 BMCWEB_LOG_ERROR("Connection went away");
169 return;
170 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800171
172 // Call the done handler regardless of whether we
173 // errored, but before we close things out
174 onDone();
175
176 if (ec)
177 {
Ed Tanous62598e32023-07-17 17:06:25 -0700178 BMCWEB_LOG_ERROR("Error in ws.async_write {}", ec);
Ed Tanous863c1c22022-02-21 21:33:06 -0800179 self->close("write error");
180 }
181 });
182 }
183
Ed Tanous1abe55e2018-09-05 08:30:59 -0700184 void sendBinary(std::string&& msg) override
185 {
186 ws.binary(true);
Ed Tanous863c1c22022-02-21 21:33:06 -0800187 outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()),
188 boost::asio::buffer(msg)));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700189 doWrite();
190 }
191
Ed Tanous26ccae32023-02-16 10:28:44 -0800192 void sendText(std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700193 {
194 ws.text(true);
Ed Tanous863c1c22022-02-21 21:33:06 -0800195 outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()),
196 boost::asio::buffer(msg)));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700197 doWrite();
198 }
199
200 void sendText(std::string&& msg) override
201 {
202 ws.text(true);
Ed Tanous863c1c22022-02-21 21:33:06 -0800203 outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()),
204 boost::asio::buffer(msg)));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700205 doWrite();
206 }
207
Ed Tanous26ccae32023-02-16 10:28:44 -0800208 void close(std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700209 {
210 ws.async_close(
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200211 {boost::beast::websocket::close_code::normal, msg},
Ed Tanous5e7e2dc2023-02-16 10:37:01 -0800212 [self(shared_from_this())](const boost::system::error_code& ec) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700213 if (ec == boost::asio::error::operation_aborted)
214 {
215 return;
216 }
217 if (ec)
218 {
Ed Tanous62598e32023-07-17 17:06:25 -0700219 BMCWEB_LOG_ERROR("Error closing websocket {}", ec);
Ed Tanous002d39b2022-05-31 08:59:27 -0700220 return;
221 }
Patrick Williams5a39f772023-10-20 11:20:21 -0500222 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700223 }
224
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500225 boost::urls::url_view url() override
226 {
227 return uri;
228 }
229
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800230 void acceptDone(const std::shared_ptr<Connection>& /*self*/,
Ed Tanous52e31622024-01-23 16:31:11 -0800231 const std::unique_ptr<
Ed Tanousb2896142024-01-31 15:25:47 -0800232 boost::beast::http::request<bmcweb::HttpBody>>& /*req*/,
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800233 const boost::system::error_code& ec)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700234 {
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800235 if (ec)
236 {
237 BMCWEB_LOG_ERROR("Error in ws.async_accept {}", ec);
238 return;
239 }
Ed Tanous62598e32023-07-17 17:06:25 -0700240 BMCWEB_LOG_DEBUG("Websocket accepted connection");
Ed Tanous1abe55e2018-09-05 08:30:59 -0700241
242 if (openHandler)
243 {
zhanghch0577726382021-10-21 14:07:57 +0800244 openHandler(*this);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700245 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800246 doRead();
247 }
248
249 void deferRead() override
250 {
251 readingDefered = true;
252
253 // If we're not actively reading, we need to take ownership of
254 // ourselves for a small portion of time, do that, and clear when we
255 // resume.
256 selfOwned = shared_from_this();
257 }
258
259 void resumeRead() override
260 {
261 readingDefered = false;
262 doRead();
263
264 // No longer need to keep ourselves alive now that read is active.
265 selfOwned.reset();
Ed Tanous1abe55e2018-09-05 08:30:59 -0700266 }
267
268 void doRead()
269 {
Ed Tanous863c1c22022-02-21 21:33:06 -0800270 if (readingDefered)
271 {
272 return;
273 }
274 ws.async_read(inBuffer, [this, self(shared_from_this())](
275 const boost::beast::error_code& ec,
276 size_t bytesRead) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700277 if (ec)
278 {
279 if (ec != boost::beast::websocket::error::closed)
280 {
Ed Tanous62598e32023-07-17 17:06:25 -0700281 BMCWEB_LOG_ERROR("doRead error {}", ec);
Ed Tanous002d39b2022-05-31 08:59:27 -0700282 }
283 if (closeHandler)
284 {
Ed Tanous079360a2022-06-29 10:05:19 -0700285 std::string reason{ws.reason().reason.c_str()};
286 closeHandler(*this, reason);
Ed Tanous002d39b2022-05-31 08:59:27 -0700287 }
288 return;
289 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800290
291 handleMessage(bytesRead);
Ed Tanous002d39b2022-05-31 08:59:27 -0700292 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700293 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700294 void doWrite()
295 {
296 // If we're already doing a write, ignore the request, it will be picked
297 // up when the current write is complete
298 if (doingWrite)
299 {
300 return;
301 }
302
Ed Tanous863c1c22022-02-21 21:33:06 -0800303 if (outBuffer.size() == 0)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700304 {
305 // Done for now
306 return;
307 }
308 doingWrite = true;
Ed Tanous863c1c22022-02-21 21:33:06 -0800309 ws.async_write(outBuffer.data(), [this, self(shared_from_this())](
310 const boost::beast::error_code& ec,
311 size_t bytesSent) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700312 doingWrite = false;
Ed Tanous863c1c22022-02-21 21:33:06 -0800313 outBuffer.consume(bytesSent);
Ed Tanous002d39b2022-05-31 08:59:27 -0700314 if (ec == boost::beast::websocket::error::closed)
315 {
316 // Do nothing here. doRead handler will call the
317 // closeHandler.
318 close("Write error");
319 return;
320 }
321 if (ec)
322 {
Ed Tanous62598e32023-07-17 17:06:25 -0700323 BMCWEB_LOG_ERROR("Error in ws.async_write {}", ec);
Ed Tanous002d39b2022-05-31 08:59:27 -0700324 return;
325 }
326 doWrite();
327 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700328 }
329
330 private:
Ed Tanous863c1c22022-02-21 21:33:06 -0800331 void handleMessage(size_t bytesRead)
332 {
333 if (messageExHandler)
334 {
335 // Note, because of the interactions with the read buffers,
336 // this message handler overrides the normal message handler
337 messageExHandler(*this, inString, MessageType::Binary,
338 [this, self(shared_from_this()), bytesRead]() {
339 if (self == nullptr)
340 {
341 return;
342 }
343
344 inBuffer.consume(bytesRead);
345 inString.clear();
346
347 doRead();
348 });
349 return;
350 }
351
352 if (messageHandler)
353 {
354 messageHandler(*this, inString, ws.got_text());
355 }
356 inBuffer.consume(bytesRead);
357 inString.clear();
358 doRead();
359 }
360
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500361 boost::urls::url uri;
362
Ed Tanous2aee6ca2021-02-01 09:52:17 -0800363 boost::beast::websocket::stream<Adaptor, false> ws;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700364
Ed Tanous863c1c22022-02-21 21:33:06 -0800365 bool readingDefered = false;
Ed Tanous609145a2018-09-05 16:27:36 -0700366 std::string inString;
367 boost::asio::dynamic_string_buffer<std::string::value_type,
368 std::string::traits_type,
369 std::string::allocator_type>
370 inBuffer;
Ed Tanous863c1c22022-02-21 21:33:06 -0800371
372 boost::beast::multi_buffer outBuffer;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700373 bool doingWrite = false;
374
zhanghch0577726382021-10-21 14:07:57 +0800375 std::function<void(Connection&)> openHandler;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700376 std::function<void(Connection&, const std::string&, bool)> messageHandler;
Ed Tanous863c1c22022-02-21 21:33:06 -0800377 std::function<void(crow::websocket::Connection&, std::string_view,
378 crow::websocket::MessageType type,
379 std::function<void()>&& whenComplete)>
380 messageExHandler;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700381 std::function<void(Connection&, const std::string&)> closeHandler;
382 std::function<void(Connection&)> errorHandler;
Ed Tanous52cc1122020-07-18 13:51:21 -0700383 std::shared_ptr<persistent_data::UserSession> session;
Ed Tanous863c1c22022-02-21 21:33:06 -0800384
385 std::shared_ptr<Connection> selfOwned;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700386};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700387} // namespace websocket
388} // namespace crow