blob: be650ae78c9b57fe671ffb7a448bf413fb817053 [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 Tanous8db83742024-04-13 09:11:15 -07009#include <boost/beast/websocket/ssl.hpp>
Ed Tanous1abe55e2018-09-05 08:30:59 -070010
Gunnar Mills1214b7e2020-06-04 10:11:30 -050011#include <array>
12#include <functional>
Ed Tanous1b0044b2018-08-03 14:30:05 -070013
Ed Tanous1abe55e2018-09-05 08:30:59 -070014namespace crow
15{
16namespace websocket
17{
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020018
Ed Tanous863c1c22022-02-21 21:33:06 -080019enum class MessageType
20{
21 Binary,
22 Text,
23};
24
Ed Tanous1abe55e2018-09-05 08:30:59 -070025struct Connection : std::enable_shared_from_this<Connection>
26{
27 public:
Ed Tanous5ebb9d32023-02-27 18:20:47 -080028 Connection() = default;
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +010029
Ed Tanousecd6a3a2022-01-07 09:18:40 -080030 Connection(const Connection&) = delete;
31 Connection(Connection&&) = delete;
32 Connection& operator=(const Connection&) = delete;
33 Connection& operator=(const Connection&&) = delete;
34
Ed Tanous9eb808c2022-01-25 10:19:23 -080035 virtual void sendBinary(std::string_view msg) = 0;
Ed Tanous863c1c22022-02-21 21:33:06 -080036 virtual void sendEx(MessageType type, std::string_view msg,
37 std::function<void()>&& onDone) = 0;
Ed Tanous9eb808c2022-01-25 10:19:23 -080038 virtual void sendText(std::string_view msg) = 0;
Ed Tanous9eb808c2022-01-25 10:19:23 -080039 virtual void close(std::string_view msg = "quit") = 0;
Ed Tanous863c1c22022-02-21 21:33:06 -080040 virtual void deferRead() = 0;
41 virtual void resumeRead() = 0;
Ed Tanous2c70f802020-09-28 14:29:23 -070042 virtual boost::asio::io_context& getIoContext() = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070043 virtual ~Connection() = default;
Ninad Palsule052bcbf2023-05-30 11:10:58 -050044 virtual boost::urls::url_view url() = 0;
Ed Tanous7045c8d2017-04-03 10:04:37 -070045};
46
Gunnar Mills1214b7e2020-06-04 10:11:30 -050047template <typename Adaptor>
48class ConnectionImpl : public Connection
Ed Tanous1abe55e2018-09-05 08:30:59 -070049{
Ed Tanous5ebb9d32023-02-27 18:20:47 -080050 using self_t = ConnectionImpl<Adaptor>;
51
Ed Tanous1abe55e2018-09-05 08:30:59 -070052 public:
53 ConnectionImpl(
Ed Tanous5ebb9d32023-02-27 18:20:47 -080054 const boost::urls::url_view& urlViewIn,
55 const std::shared_ptr<persistent_data::UserSession>& sessionIn,
Ninad Palsule052bcbf2023-05-30 11:10:58 -050056 Adaptor adaptorIn, std::function<void(Connection&)> openHandlerIn,
Ed Tanous1abe55e2018-09-05 08:30:59 -070057 std::function<void(Connection&, const std::string&, bool)>
Ed Tanous8a592812022-06-04 09:06:59 -070058 messageHandlerIn,
Ed Tanous863c1c22022-02-21 21:33:06 -080059 std::function<void(crow::websocket::Connection&, std::string_view,
60 crow::websocket::MessageType type,
61 std::function<void()>&& whenComplete)>
62 messageExHandlerIn,
Ed Tanous8a592812022-06-04 09:06:59 -070063 std::function<void(Connection&, const std::string&)> closeHandlerIn,
64 std::function<void(Connection&)> errorHandlerIn) :
Patrick Williamsbd79bce2024-08-16 15:22:20 -040065 uri(urlViewIn), ws(std::move(adaptorIn)), inBuffer(inString, 131088),
Ed Tanous8a592812022-06-04 09:06:59 -070066 openHandler(std::move(openHandlerIn)),
67 messageHandler(std::move(messageHandlerIn)),
Ed Tanous863c1c22022-02-21 21:33:06 -080068 messageExHandler(std::move(messageExHandlerIn)),
Ed Tanous8a592812022-06-04 09:06:59 -070069 closeHandler(std::move(closeHandlerIn)),
Ed Tanous5ebb9d32023-02-27 18:20:47 -080070 errorHandler(std::move(errorHandlerIn)), session(sessionIn)
Ed Tanous1abe55e2018-09-05 08:30:59 -070071 {
dhineskumare02bdd962021-07-08 16:06:49 +053072 /* Turn on the timeouts on websocket stream to server role */
73 ws.set_option(boost::beast::websocket::stream_base::timeout::suggested(
74 boost::beast::role_type::server));
Ed Tanous62598e32023-07-17 17:06:25 -070075 BMCWEB_LOG_DEBUG("Creating new connection {}", logPtr(this));
Ed Tanous7045c8d2017-04-03 10:04:37 -070076 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070077
Ed Tanous2c70f802020-09-28 14:29:23 -070078 boost::asio::io_context& getIoContext() override
Ed Tanous1abe55e2018-09-05 08:30:59 -070079 {
Ed Tanous271584a2019-07-09 16:24:22 -070080 return static_cast<boost::asio::io_context&>(
81 ws.get_executor().context());
Ed Tanous911ac312017-08-15 09:37:42 -070082 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070083
Ed Tanous5ebb9d32023-02-27 18:20:47 -080084 void start(const crow::Request& req)
Ed Tanous1abe55e2018-09-05 08:30:59 -070085 {
Ed Tanous62598e32023-07-17 17:06:25 -070086 BMCWEB_LOG_DEBUG("starting connection {}", logPtr(this));
Ed Tanous7045c8d2017-04-03 10:04:37 -070087
Ed Tanousfe5b2162019-05-22 14:28:16 -070088 using bf = boost::beast::http::field;
Myung Bae1873a042024-04-01 09:27:39 -050089 std::string protocolHeader{
90 req.getHeaderValue(bf::sec_websocket_protocol)};
Ed Tanous7045c8d2017-04-03 10:04:37 -070091
Ed Tanousd4d77e32020-08-18 00:07:28 -070092 ws.set_option(boost::beast::websocket::stream_base::decorator(
Ed Tanous5ebb9d32023-02-27 18:20:47 -080093 [session{session},
94 protocolHeader](boost::beast::websocket::response_type& m) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -040095 if constexpr (!BMCWEB_INSECURE_DISABLE_CSRF)
James Feistf8aa3d22020-04-08 18:32:33 -070096 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -040097 if (session != nullptr)
Ed Tanous83328312024-05-09 15:48:09 -070098 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -040099 // use protocol for csrf checking
100 if (session->cookieAuth &&
101 !bmcweb::constantTimeStringCompare(
102 protocolHeader, session->csrfToken))
103 {
104 BMCWEB_LOG_ERROR("Websocket CSRF error");
105 m.result(boost::beast::http::status::unauthorized);
106 return;
107 }
Ed Tanous83328312024-05-09 15:48:09 -0700108 }
James Feistf8aa3d22020-04-08 18:32:33 -0700109 }
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400110 if (!protocolHeader.empty())
111 {
112 m.insert(bf::sec_websocket_protocol, protocolHeader);
113 }
Ed Tanousfe5b2162019-05-22 14:28:16 -0700114
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400115 m.insert(bf::strict_transport_security,
116 "max-age=31536000; "
117 "includeSubdomains; "
118 "preload");
119 m.insert(bf::pragma, "no-cache");
120 m.insert(bf::cache_control, "no-Store,no-Cache");
121 m.insert("Content-Security-Policy", "default-src 'self'");
122 m.insert("X-XSS-Protection", "1; "
123 "mode=block");
124 m.insert("X-Content-Type-Options", "nosniff");
125 }));
Ed Tanousd4d77e32020-08-18 00:07:28 -0700126
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800127 // Make a pointer to keep the req alive while we accept it.
Ed Tanousb2896142024-01-31 15:25:47 -0800128 using Body = boost::beast::http::request<bmcweb::HttpBody>;
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800129 std::unique_ptr<Body> mobile = std::make_unique<Body>(req.req);
130 Body* ptr = mobile.get();
Ed Tanousd4d77e32020-08-18 00:07:28 -0700131 // Perform the websocket upgrade
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800132 ws.async_accept(*ptr,
133 std::bind_front(&self_t::acceptDone, this,
134 shared_from_this(), std::move(mobile)));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700135 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700136
Ed Tanous26ccae32023-02-16 10:28:44 -0800137 void sendBinary(std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700138 {
139 ws.binary(true);
Ed Tanous863c1c22022-02-21 21:33:06 -0800140 outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()),
141 boost::asio::buffer(msg)));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700142 doWrite();
143 }
144
Ed Tanous863c1c22022-02-21 21:33:06 -0800145 void sendEx(MessageType type, std::string_view msg,
146 std::function<void()>&& onDone) override
147 {
148 if (doingWrite)
149 {
Ed Tanous62598e32023-07-17 17:06:25 -0700150 BMCWEB_LOG_CRITICAL(
151 "Cannot mix sendEx usage with sendBinary or sendText");
Ed Tanous863c1c22022-02-21 21:33:06 -0800152 onDone();
153 return;
154 }
155 ws.binary(type == MessageType::Binary);
156
157 ws.async_write(boost::asio::buffer(msg),
158 [weak(weak_from_this()), onDone{std::move(onDone)}](
159 const boost::beast::error_code& ec, size_t) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400160 std::shared_ptr<Connection> self = weak.lock();
161 if (!self)
162 {
163 BMCWEB_LOG_ERROR("Connection went away");
164 return;
165 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800166
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400167 // Call the done handler regardless of whether we
168 // errored, but before we close things out
169 onDone();
Ed Tanous863c1c22022-02-21 21:33:06 -0800170
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400171 if (ec)
172 {
173 BMCWEB_LOG_ERROR("Error in ws.async_write {}",
174 ec);
175 self->close("write error");
176 }
177 });
Ed Tanous863c1c22022-02-21 21:33:06 -0800178 }
179
Ed Tanous26ccae32023-02-16 10:28:44 -0800180 void sendText(std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700181 {
182 ws.text(true);
Ed Tanous863c1c22022-02-21 21:33:06 -0800183 outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()),
184 boost::asio::buffer(msg)));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700185 doWrite();
186 }
187
Ed Tanous26ccae32023-02-16 10:28:44 -0800188 void close(std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700189 {
190 ws.async_close(
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200191 {boost::beast::websocket::close_code::normal, msg},
Ed Tanous5e7e2dc2023-02-16 10:37:01 -0800192 [self(shared_from_this())](const boost::system::error_code& ec) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400193 if (ec == boost::asio::error::operation_aborted)
194 {
195 return;
196 }
197 if (ec)
198 {
199 BMCWEB_LOG_ERROR("Error closing websocket {}", ec);
200 return;
201 }
202 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700203 }
204
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500205 boost::urls::url_view url() override
206 {
207 return uri;
208 }
209
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800210 void acceptDone(const std::shared_ptr<Connection>& /*self*/,
Ed Tanous52e31622024-01-23 16:31:11 -0800211 const std::unique_ptr<
Ed Tanousb2896142024-01-31 15:25:47 -0800212 boost::beast::http::request<bmcweb::HttpBody>>& /*req*/,
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800213 const boost::system::error_code& ec)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700214 {
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800215 if (ec)
216 {
217 BMCWEB_LOG_ERROR("Error in ws.async_accept {}", ec);
218 return;
219 }
Ed Tanous62598e32023-07-17 17:06:25 -0700220 BMCWEB_LOG_DEBUG("Websocket accepted connection");
Ed Tanous1abe55e2018-09-05 08:30:59 -0700221
222 if (openHandler)
223 {
zhanghch0577726382021-10-21 14:07:57 +0800224 openHandler(*this);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700225 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800226 doRead();
227 }
228
229 void deferRead() override
230 {
231 readingDefered = true;
232
233 // If we're not actively reading, we need to take ownership of
234 // ourselves for a small portion of time, do that, and clear when we
235 // resume.
236 selfOwned = shared_from_this();
237 }
238
239 void resumeRead() override
240 {
241 readingDefered = false;
242 doRead();
243
244 // No longer need to keep ourselves alive now that read is active.
245 selfOwned.reset();
Ed Tanous1abe55e2018-09-05 08:30:59 -0700246 }
247
248 void doRead()
249 {
Ed Tanous863c1c22022-02-21 21:33:06 -0800250 if (readingDefered)
251 {
252 return;
253 }
254 ws.async_read(inBuffer, [this, self(shared_from_this())](
255 const boost::beast::error_code& ec,
256 size_t bytesRead) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700257 if (ec)
258 {
259 if (ec != boost::beast::websocket::error::closed)
260 {
Ed Tanous62598e32023-07-17 17:06:25 -0700261 BMCWEB_LOG_ERROR("doRead error {}", ec);
Ed Tanous002d39b2022-05-31 08:59:27 -0700262 }
263 if (closeHandler)
264 {
Ed Tanous079360a2022-06-29 10:05:19 -0700265 std::string reason{ws.reason().reason.c_str()};
266 closeHandler(*this, reason);
Ed Tanous002d39b2022-05-31 08:59:27 -0700267 }
268 return;
269 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800270
271 handleMessage(bytesRead);
Ed Tanous002d39b2022-05-31 08:59:27 -0700272 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700273 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700274 void doWrite()
275 {
276 // If we're already doing a write, ignore the request, it will be picked
277 // up when the current write is complete
278 if (doingWrite)
279 {
280 return;
281 }
282
Ed Tanous863c1c22022-02-21 21:33:06 -0800283 if (outBuffer.size() == 0)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700284 {
285 // Done for now
286 return;
287 }
288 doingWrite = true;
Ed Tanous863c1c22022-02-21 21:33:06 -0800289 ws.async_write(outBuffer.data(), [this, self(shared_from_this())](
290 const boost::beast::error_code& ec,
291 size_t bytesSent) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700292 doingWrite = false;
Ed Tanous863c1c22022-02-21 21:33:06 -0800293 outBuffer.consume(bytesSent);
Ed Tanous002d39b2022-05-31 08:59:27 -0700294 if (ec == boost::beast::websocket::error::closed)
295 {
296 // Do nothing here. doRead handler will call the
297 // closeHandler.
298 close("Write error");
299 return;
300 }
301 if (ec)
302 {
Ed Tanous62598e32023-07-17 17:06:25 -0700303 BMCWEB_LOG_ERROR("Error in ws.async_write {}", ec);
Ed Tanous002d39b2022-05-31 08:59:27 -0700304 return;
305 }
306 doWrite();
307 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700308 }
309
310 private:
Ed Tanous863c1c22022-02-21 21:33:06 -0800311 void handleMessage(size_t bytesRead)
312 {
313 if (messageExHandler)
314 {
315 // Note, because of the interactions with the read buffers,
316 // this message handler overrides the normal message handler
317 messageExHandler(*this, inString, MessageType::Binary,
318 [this, self(shared_from_this()), bytesRead]() {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400319 if (self == nullptr)
320 {
321 return;
322 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800323
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400324 inBuffer.consume(bytesRead);
325 inString.clear();
Ed Tanous863c1c22022-02-21 21:33:06 -0800326
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400327 doRead();
328 });
Ed Tanous863c1c22022-02-21 21:33:06 -0800329 return;
330 }
331
332 if (messageHandler)
333 {
334 messageHandler(*this, inString, ws.got_text());
335 }
336 inBuffer.consume(bytesRead);
337 inString.clear();
338 doRead();
339 }
340
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500341 boost::urls::url uri;
342
Ed Tanous2aee6ca2021-02-01 09:52:17 -0800343 boost::beast::websocket::stream<Adaptor, false> ws;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700344
Ed Tanous863c1c22022-02-21 21:33:06 -0800345 bool readingDefered = false;
Ed Tanous609145a2018-09-05 16:27:36 -0700346 std::string inString;
347 boost::asio::dynamic_string_buffer<std::string::value_type,
348 std::string::traits_type,
349 std::string::allocator_type>
350 inBuffer;
Ed Tanous863c1c22022-02-21 21:33:06 -0800351
352 boost::beast::multi_buffer outBuffer;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700353 bool doingWrite = false;
354
zhanghch0577726382021-10-21 14:07:57 +0800355 std::function<void(Connection&)> openHandler;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700356 std::function<void(Connection&, const std::string&, bool)> messageHandler;
Ed Tanous863c1c22022-02-21 21:33:06 -0800357 std::function<void(crow::websocket::Connection&, std::string_view,
358 crow::websocket::MessageType type,
359 std::function<void()>&& whenComplete)>
360 messageExHandler;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700361 std::function<void(Connection&, const std::string&)> closeHandler;
362 std::function<void(Connection&)> errorHandler;
Ed Tanous52cc1122020-07-18 13:51:21 -0700363 std::shared_ptr<persistent_data::UserSession> session;
Ed Tanous863c1c22022-02-21 21:33:06 -0800364
365 std::shared_ptr<Connection> selfOwned;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700366};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700367} // namespace websocket
368} // namespace crow