blob: 843aab316af074445d993ba0ee492a8ae8ca74a9 [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>
Lei YUad6dd392024-09-12 11:07:23 +00007#include <boost/asio/ssl/error.hpp>
Ed Tanous863c1c22022-02-21 21:33:06 -08008#include <boost/beast/core/multi_buffer.hpp>
Ed Tanous1b0044b2018-08-03 14:30:05 -07009#include <boost/beast/websocket.hpp>
Ed Tanous8db83742024-04-13 09:11:15 -070010#include <boost/beast/websocket/ssl.hpp>
Ed Tanous1abe55e2018-09-05 08:30:59 -070011
Gunnar Mills1214b7e2020-06-04 10:11:30 -050012#include <array>
13#include <functional>
Ed Tanous1b0044b2018-08-03 14:30:05 -070014
Ed Tanous1abe55e2018-09-05 08:30:59 -070015namespace crow
16{
17namespace websocket
18{
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020019
Ed Tanous863c1c22022-02-21 21:33:06 -080020enum class MessageType
21{
22 Binary,
23 Text,
24};
25
Ed Tanous1abe55e2018-09-05 08:30:59 -070026struct Connection : std::enable_shared_from_this<Connection>
27{
28 public:
Ed Tanous5ebb9d32023-02-27 18:20:47 -080029 Connection() = default;
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +010030
Ed Tanousecd6a3a2022-01-07 09:18:40 -080031 Connection(const Connection&) = delete;
32 Connection(Connection&&) = delete;
33 Connection& operator=(const Connection&) = delete;
34 Connection& operator=(const Connection&&) = delete;
35
Ed Tanous9eb808c2022-01-25 10:19:23 -080036 virtual void sendBinary(std::string_view msg) = 0;
Ed Tanous863c1c22022-02-21 21:33:06 -080037 virtual void sendEx(MessageType type, std::string_view msg,
38 std::function<void()>&& onDone) = 0;
Ed Tanous9eb808c2022-01-25 10:19:23 -080039 virtual void sendText(std::string_view msg) = 0;
Ed Tanous9eb808c2022-01-25 10:19:23 -080040 virtual void close(std::string_view msg = "quit") = 0;
Ed Tanous863c1c22022-02-21 21:33:06 -080041 virtual void deferRead() = 0;
42 virtual void resumeRead() = 0;
Ed Tanous2c70f802020-09-28 14:29:23 -070043 virtual boost::asio::io_context& getIoContext() = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070044 virtual ~Connection() = default;
Ninad Palsule052bcbf2023-05-30 11:10:58 -050045 virtual boost::urls::url_view url() = 0;
Ed Tanous7045c8d2017-04-03 10:04:37 -070046};
47
Gunnar Mills1214b7e2020-06-04 10:11:30 -050048template <typename Adaptor>
49class ConnectionImpl : public Connection
Ed Tanous1abe55e2018-09-05 08:30:59 -070050{
Ed Tanous5ebb9d32023-02-27 18:20:47 -080051 using self_t = ConnectionImpl<Adaptor>;
52
Ed Tanous1abe55e2018-09-05 08:30:59 -070053 public:
54 ConnectionImpl(
Ed Tanous5ebb9d32023-02-27 18:20:47 -080055 const boost::urls::url_view& urlViewIn,
56 const std::shared_ptr<persistent_data::UserSession>& sessionIn,
Ninad Palsule052bcbf2023-05-30 11:10:58 -050057 Adaptor adaptorIn, std::function<void(Connection&)> openHandlerIn,
Ed Tanous1abe55e2018-09-05 08:30:59 -070058 std::function<void(Connection&, const std::string&, bool)>
Ed Tanous8a592812022-06-04 09:06:59 -070059 messageHandlerIn,
Ed Tanous863c1c22022-02-21 21:33:06 -080060 std::function<void(crow::websocket::Connection&, std::string_view,
61 crow::websocket::MessageType type,
62 std::function<void()>&& whenComplete)>
63 messageExHandlerIn,
Ed Tanous8a592812022-06-04 09:06:59 -070064 std::function<void(Connection&, const std::string&)> closeHandlerIn,
65 std::function<void(Connection&)> errorHandlerIn) :
Patrick Williamsbd79bce2024-08-16 15:22:20 -040066 uri(urlViewIn), ws(std::move(adaptorIn)), inBuffer(inString, 131088),
Ed Tanous8a592812022-06-04 09:06:59 -070067 openHandler(std::move(openHandlerIn)),
68 messageHandler(std::move(messageHandlerIn)),
Ed Tanous863c1c22022-02-21 21:33:06 -080069 messageExHandler(std::move(messageExHandlerIn)),
Ed Tanous8a592812022-06-04 09:06:59 -070070 closeHandler(std::move(closeHandlerIn)),
Ed Tanous5ebb9d32023-02-27 18:20:47 -080071 errorHandler(std::move(errorHandlerIn)), session(sessionIn)
Ed Tanous1abe55e2018-09-05 08:30:59 -070072 {
dhineskumare02bdd962021-07-08 16:06:49 +053073 /* Turn on the timeouts on websocket stream to server role */
74 ws.set_option(boost::beast::websocket::stream_base::timeout::suggested(
75 boost::beast::role_type::server));
Ed Tanous62598e32023-07-17 17:06:25 -070076 BMCWEB_LOG_DEBUG("Creating new connection {}", logPtr(this));
Ed Tanous7045c8d2017-04-03 10:04:37 -070077 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070078
Ed Tanous2c70f802020-09-28 14:29:23 -070079 boost::asio::io_context& getIoContext() override
Ed Tanous1abe55e2018-09-05 08:30:59 -070080 {
Ed Tanous271584a2019-07-09 16:24:22 -070081 return static_cast<boost::asio::io_context&>(
82 ws.get_executor().context());
Ed Tanous911ac312017-08-15 09:37:42 -070083 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070084
Ed Tanous5ebb9d32023-02-27 18:20:47 -080085 void start(const crow::Request& req)
Ed Tanous1abe55e2018-09-05 08:30:59 -070086 {
Ed Tanous62598e32023-07-17 17:06:25 -070087 BMCWEB_LOG_DEBUG("starting connection {}", logPtr(this));
Ed Tanous7045c8d2017-04-03 10:04:37 -070088
Ed Tanousfe5b2162019-05-22 14:28:16 -070089 using bf = boost::beast::http::field;
Myung Bae1873a042024-04-01 09:27:39 -050090 std::string protocolHeader{
91 req.getHeaderValue(bf::sec_websocket_protocol)};
Ed Tanous7045c8d2017-04-03 10:04:37 -070092
Ed Tanousd4d77e32020-08-18 00:07:28 -070093 ws.set_option(boost::beast::websocket::stream_base::decorator(
Ed Tanous5ebb9d32023-02-27 18:20:47 -080094 [session{session},
95 protocolHeader](boost::beast::websocket::response_type& m) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -040096 if constexpr (!BMCWEB_INSECURE_DISABLE_CSRF)
James Feistf8aa3d22020-04-08 18:32:33 -070097 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -040098 if (session != nullptr)
Ed Tanous83328312024-05-09 15:48:09 -070099 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400100 // use protocol for csrf checking
101 if (session->cookieAuth &&
102 !bmcweb::constantTimeStringCompare(
103 protocolHeader, session->csrfToken))
104 {
105 BMCWEB_LOG_ERROR("Websocket CSRF error");
106 m.result(boost::beast::http::status::unauthorized);
107 return;
108 }
Ed Tanous83328312024-05-09 15:48:09 -0700109 }
James Feistf8aa3d22020-04-08 18:32:33 -0700110 }
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400111 if (!protocolHeader.empty())
112 {
113 m.insert(bf::sec_websocket_protocol, protocolHeader);
114 }
Ed Tanousfe5b2162019-05-22 14:28:16 -0700115
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400116 m.insert(bf::strict_transport_security,
117 "max-age=31536000; "
118 "includeSubdomains; "
119 "preload");
120 m.insert(bf::pragma, "no-cache");
121 m.insert(bf::cache_control, "no-Store,no-Cache");
122 m.insert("Content-Security-Policy", "default-src 'self'");
123 m.insert("X-XSS-Protection", "1; "
124 "mode=block");
125 m.insert("X-Content-Type-Options", "nosniff");
126 }));
Ed Tanousd4d77e32020-08-18 00:07:28 -0700127
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800128 // Make a pointer to keep the req alive while we accept it.
Ed Tanousb2896142024-01-31 15:25:47 -0800129 using Body = boost::beast::http::request<bmcweb::HttpBody>;
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800130 std::unique_ptr<Body> mobile = std::make_unique<Body>(req.req);
131 Body* ptr = mobile.get();
Ed Tanousd4d77e32020-08-18 00:07:28 -0700132 // Perform the websocket upgrade
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800133 ws.async_accept(*ptr,
134 std::bind_front(&self_t::acceptDone, this,
135 shared_from_this(), std::move(mobile)));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700136 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700137
Ed Tanous26ccae32023-02-16 10:28:44 -0800138 void sendBinary(std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700139 {
140 ws.binary(true);
Ed Tanous863c1c22022-02-21 21:33:06 -0800141 outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()),
142 boost::asio::buffer(msg)));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700143 doWrite();
144 }
145
Ed Tanous863c1c22022-02-21 21:33:06 -0800146 void sendEx(MessageType type, std::string_view msg,
147 std::function<void()>&& onDone) override
148 {
149 if (doingWrite)
150 {
Ed Tanous62598e32023-07-17 17:06:25 -0700151 BMCWEB_LOG_CRITICAL(
152 "Cannot mix sendEx usage with sendBinary or sendText");
Ed Tanous863c1c22022-02-21 21:33:06 -0800153 onDone();
154 return;
155 }
156 ws.binary(type == MessageType::Binary);
157
158 ws.async_write(boost::asio::buffer(msg),
159 [weak(weak_from_this()), onDone{std::move(onDone)}](
160 const boost::beast::error_code& ec, size_t) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400161 std::shared_ptr<Connection> self = weak.lock();
162 if (!self)
163 {
164 BMCWEB_LOG_ERROR("Connection went away");
165 return;
166 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800167
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400168 // Call the done handler regardless of whether we
169 // errored, but before we close things out
170 onDone();
Ed Tanous863c1c22022-02-21 21:33:06 -0800171
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400172 if (ec)
173 {
174 BMCWEB_LOG_ERROR("Error in ws.async_write {}",
175 ec);
176 self->close("write error");
177 }
178 });
Ed Tanous863c1c22022-02-21 21:33:06 -0800179 }
180
Ed Tanous26ccae32023-02-16 10:28:44 -0800181 void sendText(std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700182 {
183 ws.text(true);
Ed Tanous863c1c22022-02-21 21:33:06 -0800184 outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()),
185 boost::asio::buffer(msg)));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700186 doWrite();
187 }
188
Ed Tanous26ccae32023-02-16 10:28:44 -0800189 void close(std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700190 {
191 ws.async_close(
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200192 {boost::beast::websocket::close_code::normal, msg},
Ed Tanous5e7e2dc2023-02-16 10:37:01 -0800193 [self(shared_from_this())](const boost::system::error_code& ec) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400194 if (ec == boost::asio::error::operation_aborted)
195 {
196 return;
197 }
198 if (ec)
199 {
200 BMCWEB_LOG_ERROR("Error closing websocket {}", ec);
201 return;
202 }
203 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700204 }
205
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500206 boost::urls::url_view url() override
207 {
208 return uri;
209 }
210
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800211 void acceptDone(const std::shared_ptr<Connection>& /*self*/,
Ed Tanous52e31622024-01-23 16:31:11 -0800212 const std::unique_ptr<
Ed Tanousb2896142024-01-31 15:25:47 -0800213 boost::beast::http::request<bmcweb::HttpBody>>& /*req*/,
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800214 const boost::system::error_code& ec)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700215 {
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800216 if (ec)
217 {
218 BMCWEB_LOG_ERROR("Error in ws.async_accept {}", ec);
219 return;
220 }
Ed Tanous62598e32023-07-17 17:06:25 -0700221 BMCWEB_LOG_DEBUG("Websocket accepted connection");
Ed Tanous1abe55e2018-09-05 08:30:59 -0700222
223 if (openHandler)
224 {
zhanghch0577726382021-10-21 14:07:57 +0800225 openHandler(*this);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700226 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800227 doRead();
228 }
229
230 void deferRead() override
231 {
232 readingDefered = true;
233
234 // If we're not actively reading, we need to take ownership of
235 // ourselves for a small portion of time, do that, and clear when we
236 // resume.
237 selfOwned = shared_from_this();
238 }
239
240 void resumeRead() override
241 {
242 readingDefered = false;
243 doRead();
244
245 // No longer need to keep ourselves alive now that read is active.
246 selfOwned.reset();
Ed Tanous1abe55e2018-09-05 08:30:59 -0700247 }
248
249 void doRead()
250 {
Ed Tanous863c1c22022-02-21 21:33:06 -0800251 if (readingDefered)
252 {
253 return;
254 }
255 ws.async_read(inBuffer, [this, self(shared_from_this())](
256 const boost::beast::error_code& ec,
257 size_t bytesRead) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700258 if (ec)
259 {
Lei YUad6dd392024-09-12 11:07:23 +0000260 if (ec != boost::beast::websocket::error::closed &&
261 ec != boost::asio::error::eof &&
262 ec != boost::asio::ssl::error::stream_truncated)
Ed Tanous002d39b2022-05-31 08:59:27 -0700263 {
Ed Tanous62598e32023-07-17 17:06:25 -0700264 BMCWEB_LOG_ERROR("doRead error {}", ec);
Ed Tanous002d39b2022-05-31 08:59:27 -0700265 }
266 if (closeHandler)
267 {
Ed Tanous079360a2022-06-29 10:05:19 -0700268 std::string reason{ws.reason().reason.c_str()};
269 closeHandler(*this, reason);
Ed Tanous002d39b2022-05-31 08:59:27 -0700270 }
271 return;
272 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800273
274 handleMessage(bytesRead);
Ed Tanous002d39b2022-05-31 08:59:27 -0700275 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700276 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700277 void doWrite()
278 {
279 // If we're already doing a write, ignore the request, it will be picked
280 // up when the current write is complete
281 if (doingWrite)
282 {
283 return;
284 }
285
Ed Tanous863c1c22022-02-21 21:33:06 -0800286 if (outBuffer.size() == 0)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700287 {
288 // Done for now
289 return;
290 }
291 doingWrite = true;
Ed Tanous863c1c22022-02-21 21:33:06 -0800292 ws.async_write(outBuffer.data(), [this, self(shared_from_this())](
293 const boost::beast::error_code& ec,
294 size_t bytesSent) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700295 doingWrite = false;
Ed Tanous863c1c22022-02-21 21:33:06 -0800296 outBuffer.consume(bytesSent);
Ed Tanous002d39b2022-05-31 08:59:27 -0700297 if (ec == boost::beast::websocket::error::closed)
298 {
299 // Do nothing here. doRead handler will call the
300 // closeHandler.
301 close("Write error");
302 return;
303 }
304 if (ec)
305 {
Ed Tanous62598e32023-07-17 17:06:25 -0700306 BMCWEB_LOG_ERROR("Error in ws.async_write {}", ec);
Ed Tanous002d39b2022-05-31 08:59:27 -0700307 return;
308 }
309 doWrite();
310 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700311 }
312
313 private:
Ed Tanous863c1c22022-02-21 21:33:06 -0800314 void handleMessage(size_t bytesRead)
315 {
316 if (messageExHandler)
317 {
318 // Note, because of the interactions with the read buffers,
319 // this message handler overrides the normal message handler
320 messageExHandler(*this, inString, MessageType::Binary,
321 [this, self(shared_from_this()), bytesRead]() {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400322 if (self == nullptr)
323 {
324 return;
325 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800326
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400327 inBuffer.consume(bytesRead);
328 inString.clear();
Ed Tanous863c1c22022-02-21 21:33:06 -0800329
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400330 doRead();
331 });
Ed Tanous863c1c22022-02-21 21:33:06 -0800332 return;
333 }
334
335 if (messageHandler)
336 {
337 messageHandler(*this, inString, ws.got_text());
338 }
339 inBuffer.consume(bytesRead);
340 inString.clear();
341 doRead();
342 }
343
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500344 boost::urls::url uri;
345
Ed Tanous2aee6ca2021-02-01 09:52:17 -0800346 boost::beast::websocket::stream<Adaptor, false> ws;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700347
Ed Tanous863c1c22022-02-21 21:33:06 -0800348 bool readingDefered = false;
Ed Tanous609145a2018-09-05 16:27:36 -0700349 std::string inString;
350 boost::asio::dynamic_string_buffer<std::string::value_type,
351 std::string::traits_type,
352 std::string::allocator_type>
353 inBuffer;
Ed Tanous863c1c22022-02-21 21:33:06 -0800354
355 boost::beast::multi_buffer outBuffer;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700356 bool doingWrite = false;
357
zhanghch0577726382021-10-21 14:07:57 +0800358 std::function<void(Connection&)> openHandler;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700359 std::function<void(Connection&, const std::string&, bool)> messageHandler;
Ed Tanous863c1c22022-02-21 21:33:06 -0800360 std::function<void(crow::websocket::Connection&, std::string_view,
361 crow::websocket::MessageType type,
362 std::function<void()>&& whenComplete)>
363 messageExHandler;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700364 std::function<void(Connection&, const std::string&)> closeHandler;
365 std::function<void(Connection&)> errorHandler;
Ed Tanous52cc1122020-07-18 13:51:21 -0700366 std::shared_ptr<persistent_data::UserSession> session;
Ed Tanous863c1c22022-02-21 21:33:06 -0800367
368 std::shared_ptr<Connection> selfOwned;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700369};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700370} // namespace websocket
371} // namespace crow