blob: 388a3e40313aeddcf5b003d14c5c3909f2c617a9 [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:
Ed Tanous5ebb9d32023-02-27 18:20:47 -080030 Connection() = default;
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;
Ed Tanous7045c8d2017-04-03 10:04:37 -070049};
50
Gunnar Mills1214b7e2020-06-04 10:11:30 -050051template <typename Adaptor>
52class ConnectionImpl : public Connection
Ed Tanous1abe55e2018-09-05 08:30:59 -070053{
Ed Tanous5ebb9d32023-02-27 18:20:47 -080054 using self_t = ConnectionImpl<Adaptor>;
55
Ed Tanous1abe55e2018-09-05 08:30:59 -070056 public:
57 ConnectionImpl(
Ed Tanous5ebb9d32023-02-27 18:20:47 -080058 const boost::urls::url_view& urlViewIn,
59 const std::shared_ptr<persistent_data::UserSession>& sessionIn,
Ninad Palsule052bcbf2023-05-30 11:10:58 -050060 Adaptor adaptorIn, std::function<void(Connection&)> openHandlerIn,
Ed Tanous1abe55e2018-09-05 08:30:59 -070061 std::function<void(Connection&, const std::string&, bool)>
Ed Tanous8a592812022-06-04 09:06:59 -070062 messageHandlerIn,
Ed Tanous863c1c22022-02-21 21:33:06 -080063 std::function<void(crow::websocket::Connection&, std::string_view,
64 crow::websocket::MessageType type,
65 std::function<void()>&& whenComplete)>
66 messageExHandlerIn,
Ed Tanous8a592812022-06-04 09:06:59 -070067 std::function<void(Connection&, const std::string&)> closeHandlerIn,
68 std::function<void(Connection&)> errorHandlerIn) :
Ed Tanous5ebb9d32023-02-27 18:20:47 -080069 uri(urlViewIn),
70 ws(std::move(adaptorIn)), inBuffer(inString, 131088),
Ed Tanous8a592812022-06-04 09:06:59 -070071 openHandler(std::move(openHandlerIn)),
72 messageHandler(std::move(messageHandlerIn)),
Ed Tanous863c1c22022-02-21 21:33:06 -080073 messageExHandler(std::move(messageExHandlerIn)),
Ed Tanous8a592812022-06-04 09:06:59 -070074 closeHandler(std::move(closeHandlerIn)),
Ed Tanous5ebb9d32023-02-27 18:20:47 -080075 errorHandler(std::move(errorHandlerIn)), session(sessionIn)
Ed Tanous1abe55e2018-09-05 08:30:59 -070076 {
dhineskumare02bdd962021-07-08 16:06:49 +053077 /* Turn on the timeouts on websocket stream to server role */
78 ws.set_option(boost::beast::websocket::stream_base::timeout::suggested(
79 boost::beast::role_type::server));
Ed Tanous62598e32023-07-17 17:06:25 -070080 BMCWEB_LOG_DEBUG("Creating new connection {}", logPtr(this));
Ed Tanous7045c8d2017-04-03 10:04:37 -070081 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070082
Ed Tanous2c70f802020-09-28 14:29:23 -070083 boost::asio::io_context& getIoContext() override
Ed Tanous1abe55e2018-09-05 08:30:59 -070084 {
Ed Tanous271584a2019-07-09 16:24:22 -070085 return static_cast<boost::asio::io_context&>(
86 ws.get_executor().context());
Ed Tanous911ac312017-08-15 09:37:42 -070087 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070088
Ed Tanous5ebb9d32023-02-27 18:20:47 -080089 void start(const crow::Request& req)
Ed Tanous1abe55e2018-09-05 08:30:59 -070090 {
Ed Tanous62598e32023-07-17 17:06:25 -070091 BMCWEB_LOG_DEBUG("starting connection {}", logPtr(this));
Ed Tanous7045c8d2017-04-03 10:04:37 -070092
Ed Tanousfe5b2162019-05-22 14:28:16 -070093 using bf = boost::beast::http::field;
Ed Tanous5ebb9d32023-02-27 18:20:47 -080094 std::string protocolHeader = req.req[bf::sec_websocket_protocol];
Ed Tanous7045c8d2017-04-03 10:04:37 -070095
Ed Tanousd4d77e32020-08-18 00:07:28 -070096 ws.set_option(boost::beast::websocket::stream_base::decorator(
Ed Tanous5ebb9d32023-02-27 18:20:47 -080097 [session{session},
98 protocolHeader](boost::beast::websocket::response_type& m) {
James Feistf8aa3d22020-04-08 18:32:33 -070099
100#ifndef BMCWEB_INSECURE_DISABLE_CSRF_PREVENTION
Ed Tanous002d39b2022-05-31 08:59:27 -0700101 if (session != nullptr)
102 {
103 // use protocol for csrf checking
Ed Tanous7e9c08e2023-06-16 11:29:37 -0700104 if (session->cookieAuth &&
105 !crow::utility::constantTimeStringCompare(
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800106 protocolHeader, session->csrfToken))
James Feistf8aa3d22020-04-08 18:32:33 -0700107 {
Ed Tanous62598e32023-07-17 17:06:25 -0700108 BMCWEB_LOG_ERROR("Websocket CSRF error");
Ed Tanous002d39b2022-05-31 08:59:27 -0700109 m.result(boost::beast::http::status::unauthorized);
110 return;
James Feistf8aa3d22020-04-08 18:32:33 -0700111 }
Ed Tanous002d39b2022-05-31 08:59:27 -0700112 }
James Feistf8aa3d22020-04-08 18:32:33 -0700113#endif
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800114 if (!protocolHeader.empty())
Ed Tanous002d39b2022-05-31 08:59:27 -0700115 {
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800116 m.insert(bf::sec_websocket_protocol, protocolHeader);
Ed Tanous002d39b2022-05-31 08:59:27 -0700117 }
Ed Tanousfe5b2162019-05-22 14:28:16 -0700118
Ed Tanous002d39b2022-05-31 08:59:27 -0700119 m.insert(bf::strict_transport_security, "max-age=31536000; "
120 "includeSubdomains; "
121 "preload");
122 m.insert(bf::pragma, "no-cache");
123 m.insert(bf::cache_control, "no-Store,no-Cache");
124 m.insert("Content-Security-Policy", "default-src 'self'");
125 m.insert("X-XSS-Protection", "1; "
126 "mode=block");
127 m.insert("X-Content-Type-Options", "nosniff");
128 }));
Ed Tanousd4d77e32020-08-18 00:07:28 -0700129
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800130 // Make a pointer to keep the req alive while we accept it.
Ed Tanous52e31622024-01-23 16:31:11 -0800131 using Body = boost::beast::http::request<bmcweb::FileBody>;
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800132 std::unique_ptr<Body> mobile = std::make_unique<Body>(req.req);
133 Body* ptr = mobile.get();
Ed Tanousd4d77e32020-08-18 00:07:28 -0700134 // Perform the websocket upgrade
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800135 ws.async_accept(*ptr,
136 std::bind_front(&self_t::acceptDone, this,
137 shared_from_this(), std::move(mobile)));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700138 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700139
Ed Tanous26ccae32023-02-16 10:28:44 -0800140 void sendBinary(std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700141 {
142 ws.binary(true);
Ed Tanous863c1c22022-02-21 21:33:06 -0800143 outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()),
144 boost::asio::buffer(msg)));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700145 doWrite();
146 }
147
Ed Tanous863c1c22022-02-21 21:33:06 -0800148 void sendEx(MessageType type, std::string_view msg,
149 std::function<void()>&& onDone) override
150 {
151 if (doingWrite)
152 {
Ed Tanous62598e32023-07-17 17:06:25 -0700153 BMCWEB_LOG_CRITICAL(
154 "Cannot mix sendEx usage with sendBinary or sendText");
Ed Tanous863c1c22022-02-21 21:33:06 -0800155 onDone();
156 return;
157 }
158 ws.binary(type == MessageType::Binary);
159
160 ws.async_write(boost::asio::buffer(msg),
161 [weak(weak_from_this()), onDone{std::move(onDone)}](
162 const boost::beast::error_code& ec, size_t) {
163 std::shared_ptr<Connection> self = weak.lock();
zhaogang.0108a8894202023-12-22 08:53:40 +0000164 if (!self)
165 {
166 BMCWEB_LOG_ERROR("Connection went away");
167 return;
168 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800169
170 // Call the done handler regardless of whether we
171 // errored, but before we close things out
172 onDone();
173
174 if (ec)
175 {
Ed Tanous62598e32023-07-17 17:06:25 -0700176 BMCWEB_LOG_ERROR("Error in ws.async_write {}", ec);
Ed Tanous863c1c22022-02-21 21:33:06 -0800177 self->close("write error");
178 }
179 });
180 }
181
Ed Tanous1abe55e2018-09-05 08:30:59 -0700182 void sendBinary(std::string&& msg) override
183 {
184 ws.binary(true);
Ed Tanous863c1c22022-02-21 21:33:06 -0800185 outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()),
186 boost::asio::buffer(msg)));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700187 doWrite();
188 }
189
Ed Tanous26ccae32023-02-16 10:28:44 -0800190 void sendText(std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700191 {
192 ws.text(true);
Ed Tanous863c1c22022-02-21 21:33:06 -0800193 outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()),
194 boost::asio::buffer(msg)));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700195 doWrite();
196 }
197
198 void sendText(std::string&& msg) override
199 {
200 ws.text(true);
Ed Tanous863c1c22022-02-21 21:33:06 -0800201 outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()),
202 boost::asio::buffer(msg)));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700203 doWrite();
204 }
205
Ed Tanous26ccae32023-02-16 10:28:44 -0800206 void close(std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700207 {
208 ws.async_close(
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200209 {boost::beast::websocket::close_code::normal, msg},
Ed Tanous5e7e2dc2023-02-16 10:37:01 -0800210 [self(shared_from_this())](const boost::system::error_code& ec) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700211 if (ec == boost::asio::error::operation_aborted)
212 {
213 return;
214 }
215 if (ec)
216 {
Ed Tanous62598e32023-07-17 17:06:25 -0700217 BMCWEB_LOG_ERROR("Error closing websocket {}", ec);
Ed Tanous002d39b2022-05-31 08:59:27 -0700218 return;
219 }
Patrick Williams5a39f772023-10-20 11:20:21 -0500220 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700221 }
222
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500223 boost::urls::url_view url() override
224 {
225 return uri;
226 }
227
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800228 void acceptDone(const std::shared_ptr<Connection>& /*self*/,
Ed Tanous52e31622024-01-23 16:31:11 -0800229 const std::unique_ptr<
230 boost::beast::http::request<bmcweb::FileBody>>& /*req*/,
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800231 const boost::system::error_code& ec)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700232 {
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800233 if (ec)
234 {
235 BMCWEB_LOG_ERROR("Error in ws.async_accept {}", ec);
236 return;
237 }
Ed Tanous62598e32023-07-17 17:06:25 -0700238 BMCWEB_LOG_DEBUG("Websocket accepted connection");
Ed Tanous1abe55e2018-09-05 08:30:59 -0700239
240 if (openHandler)
241 {
zhanghch0577726382021-10-21 14:07:57 +0800242 openHandler(*this);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700243 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800244 doRead();
245 }
246
247 void deferRead() override
248 {
249 readingDefered = true;
250
251 // If we're not actively reading, we need to take ownership of
252 // ourselves for a small portion of time, do that, and clear when we
253 // resume.
254 selfOwned = shared_from_this();
255 }
256
257 void resumeRead() override
258 {
259 readingDefered = false;
260 doRead();
261
262 // No longer need to keep ourselves alive now that read is active.
263 selfOwned.reset();
Ed Tanous1abe55e2018-09-05 08:30:59 -0700264 }
265
266 void doRead()
267 {
Ed Tanous863c1c22022-02-21 21:33:06 -0800268 if (readingDefered)
269 {
270 return;
271 }
272 ws.async_read(inBuffer, [this, self(shared_from_this())](
273 const boost::beast::error_code& ec,
274 size_t bytesRead) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700275 if (ec)
276 {
277 if (ec != boost::beast::websocket::error::closed)
278 {
Ed Tanous62598e32023-07-17 17:06:25 -0700279 BMCWEB_LOG_ERROR("doRead error {}", ec);
Ed Tanous002d39b2022-05-31 08:59:27 -0700280 }
281 if (closeHandler)
282 {
Ed Tanous079360a2022-06-29 10:05:19 -0700283 std::string reason{ws.reason().reason.c_str()};
284 closeHandler(*this, reason);
Ed Tanous002d39b2022-05-31 08:59:27 -0700285 }
286 return;
287 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800288
289 handleMessage(bytesRead);
Ed Tanous002d39b2022-05-31 08:59:27 -0700290 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700291 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700292 void doWrite()
293 {
294 // If we're already doing a write, ignore the request, it will be picked
295 // up when the current write is complete
296 if (doingWrite)
297 {
298 return;
299 }
300
Ed Tanous863c1c22022-02-21 21:33:06 -0800301 if (outBuffer.size() == 0)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700302 {
303 // Done for now
304 return;
305 }
306 doingWrite = true;
Ed Tanous863c1c22022-02-21 21:33:06 -0800307 ws.async_write(outBuffer.data(), [this, self(shared_from_this())](
308 const boost::beast::error_code& ec,
309 size_t bytesSent) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700310 doingWrite = false;
Ed Tanous863c1c22022-02-21 21:33:06 -0800311 outBuffer.consume(bytesSent);
Ed Tanous002d39b2022-05-31 08:59:27 -0700312 if (ec == boost::beast::websocket::error::closed)
313 {
314 // Do nothing here. doRead handler will call the
315 // closeHandler.
316 close("Write error");
317 return;
318 }
319 if (ec)
320 {
Ed Tanous62598e32023-07-17 17:06:25 -0700321 BMCWEB_LOG_ERROR("Error in ws.async_write {}", ec);
Ed Tanous002d39b2022-05-31 08:59:27 -0700322 return;
323 }
324 doWrite();
325 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700326 }
327
328 private:
Ed Tanous863c1c22022-02-21 21:33:06 -0800329 void handleMessage(size_t bytesRead)
330 {
331 if (messageExHandler)
332 {
333 // Note, because of the interactions with the read buffers,
334 // this message handler overrides the normal message handler
335 messageExHandler(*this, inString, MessageType::Binary,
336 [this, self(shared_from_this()), bytesRead]() {
337 if (self == nullptr)
338 {
339 return;
340 }
341
342 inBuffer.consume(bytesRead);
343 inString.clear();
344
345 doRead();
346 });
347 return;
348 }
349
350 if (messageHandler)
351 {
352 messageHandler(*this, inString, ws.got_text());
353 }
354 inBuffer.consume(bytesRead);
355 inString.clear();
356 doRead();
357 }
358
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500359 boost::urls::url uri;
360
Ed Tanous2aee6ca2021-02-01 09:52:17 -0800361 boost::beast::websocket::stream<Adaptor, false> ws;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700362
Ed Tanous863c1c22022-02-21 21:33:06 -0800363 bool readingDefered = false;
Ed Tanous609145a2018-09-05 16:27:36 -0700364 std::string inString;
365 boost::asio::dynamic_string_buffer<std::string::value_type,
366 std::string::traits_type,
367 std::string::allocator_type>
368 inBuffer;
Ed Tanous863c1c22022-02-21 21:33:06 -0800369
370 boost::beast::multi_buffer outBuffer;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700371 bool doingWrite = false;
372
zhanghch0577726382021-10-21 14:07:57 +0800373 std::function<void(Connection&)> openHandler;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700374 std::function<void(Connection&, const std::string&, bool)> messageHandler;
Ed Tanous863c1c22022-02-21 21:33:06 -0800375 std::function<void(crow::websocket::Connection&, std::string_view,
376 crow::websocket::MessageType type,
377 std::function<void()>&& whenComplete)>
378 messageExHandler;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700379 std::function<void(Connection&, const std::string&)> closeHandler;
380 std::function<void(Connection&)> errorHandler;
Ed Tanous52cc1122020-07-18 13:51:21 -0700381 std::shared_ptr<persistent_data::UserSession> session;
Ed Tanous863c1c22022-02-21 21:33:06 -0800382
383 std::shared_ptr<Connection> selfOwned;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700384};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700385} // namespace websocket
386} // namespace crow