blob: 9a5aa29737d3060cd73abfca4237e995bf291666 [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 Tanouse551b5f2023-02-27 14:19:07 -080030 explicit Connection(const crow::Request& reqIn) : req(reqIn.req)
Ed Tanous23a21a12020-07-25 04:45:05 +000031 {}
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;
Ed Tanous7045c8d2017-04-03 10:04:37 -070049
Jan Sowinskiee52ae12020-01-09 16:28:32 +000050 boost::beast::http::request<boost::beast::http::string_body> req;
Ed Tanous7045c8d2017-04-03 10:04:37 -070051};
52
Gunnar Mills1214b7e2020-06-04 10:11:30 -050053template <typename Adaptor>
54class ConnectionImpl : public Connection
Ed Tanous1abe55e2018-09-05 08:30:59 -070055{
56 public:
57 ConnectionImpl(
Jan Sowinskiee52ae12020-01-09 16:28:32 +000058 const crow::Request& reqIn, Adaptor adaptorIn,
Ed Tanous8a592812022-06-04 09:06:59 -070059 std::function<void(Connection&)> openHandlerIn,
Ed Tanous1abe55e2018-09-05 08:30:59 -070060 std::function<void(Connection&, const std::string&, bool)>
Ed Tanous8a592812022-06-04 09:06:59 -070061 messageHandlerIn,
Ed Tanous863c1c22022-02-21 21:33:06 -080062 std::function<void(crow::websocket::Connection&, std::string_view,
63 crow::websocket::MessageType type,
64 std::function<void()>&& whenComplete)>
65 messageExHandlerIn,
Ed Tanous8a592812022-06-04 09:06:59 -070066 std::function<void(Connection&, const std::string&)> closeHandlerIn,
67 std::function<void(Connection&)> errorHandlerIn) :
Ed Tanouse551b5f2023-02-27 14:19:07 -080068 Connection(reqIn),
Ed Tanouse05aec52022-01-25 10:28:56 -080069 ws(std::move(adaptorIn)), inBuffer(inString, 131088),
Ed Tanous8a592812022-06-04 09:06:59 -070070 openHandler(std::move(openHandlerIn)),
71 messageHandler(std::move(messageHandlerIn)),
Ed Tanous863c1c22022-02-21 21:33:06 -080072 messageExHandler(std::move(messageExHandlerIn)),
Ed Tanous8a592812022-06-04 09:06:59 -070073 closeHandler(std::move(closeHandlerIn)),
74 errorHandler(std::move(errorHandlerIn)), session(reqIn.session)
Ed Tanous1abe55e2018-09-05 08:30:59 -070075 {
dhineskumare02bdd962021-07-08 16:06:49 +053076 /* Turn on the timeouts on websocket stream to server role */
77 ws.set_option(boost::beast::websocket::stream_base::timeout::suggested(
78 boost::beast::role_type::server));
Ed Tanous1abe55e2018-09-05 08:30:59 -070079 BMCWEB_LOG_DEBUG << "Creating new connection " << this;
Ed Tanous7045c8d2017-04-03 10:04:37 -070080 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070081
Ed Tanous2c70f802020-09-28 14:29:23 -070082 boost::asio::io_context& getIoContext() override
Ed Tanous1abe55e2018-09-05 08:30:59 -070083 {
Ed Tanous271584a2019-07-09 16:24:22 -070084 return static_cast<boost::asio::io_context&>(
85 ws.get_executor().context());
Ed Tanous911ac312017-08-15 09:37:42 -070086 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070087
Ed Tanous1abe55e2018-09-05 08:30:59 -070088 void start()
89 {
90 BMCWEB_LOG_DEBUG << "starting connection " << this;
Ed Tanous7045c8d2017-04-03 10:04:37 -070091
Ed Tanousfe5b2162019-05-22 14:28:16 -070092 using bf = boost::beast::http::field;
93
Jan Sowinskiee52ae12020-01-09 16:28:32 +000094 std::string_view protocol = 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(
James Feistf8aa3d22020-04-08 18:32:33 -070097 [session{session}, protocol{std::string(protocol)}](
Ed Tanous1abe55e2018-09-05 08:30:59 -070098 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
104 if (session->cookieAuth &&
105 !crow::utility::constantTimeStringCompare(
106 protocol, session->csrfToken))
James Feistf8aa3d22020-04-08 18:32:33 -0700107 {
Ed Tanous002d39b2022-05-31 08:59:27 -0700108 BMCWEB_LOG_ERROR << "Websocket CSRF error";
109 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 Tanous002d39b2022-05-31 08:59:27 -0700114 if (!protocol.empty())
115 {
116 m.insert(bf::sec_websocket_protocol, protocol);
117 }
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
130 // Perform the websocket upgrade
131 ws.async_accept(req, [this, self(shared_from_this())](
Ed Tanous5e7e2dc2023-02-16 10:37:01 -0800132 const boost::system::error_code& ec) {
Ed Tanousd4d77e32020-08-18 00:07:28 -0700133 if (ec)
134 {
135 BMCWEB_LOG_ERROR << "Error in ws.async_accept " << ec;
136 return;
137 }
138 acceptDone();
139 });
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 {
155 BMCWEB_LOG_CRITICAL
156 << "Cannot mix sendEx usage with sendBinary or sendText";
157 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();
166
167 // Call the done handler regardless of whether we
168 // errored, but before we close things out
169 onDone();
170
171 if (ec)
172 {
173 BMCWEB_LOG_ERROR << "Error in ws.async_write " << ec;
174 self->close("write error");
175 }
176 });
177 }
178
Ed Tanous1abe55e2018-09-05 08:30:59 -0700179 void sendBinary(std::string&& msg) override
180 {
181 ws.binary(true);
Ed Tanous863c1c22022-02-21 21:33:06 -0800182 outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()),
183 boost::asio::buffer(msg)));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700184 doWrite();
185 }
186
Ed Tanous26ccae32023-02-16 10:28:44 -0800187 void sendText(std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700188 {
189 ws.text(true);
Ed Tanous863c1c22022-02-21 21:33:06 -0800190 outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()),
191 boost::asio::buffer(msg)));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700192 doWrite();
193 }
194
195 void sendText(std::string&& msg) override
196 {
197 ws.text(true);
Ed Tanous863c1c22022-02-21 21:33:06 -0800198 outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()),
199 boost::asio::buffer(msg)));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700200 doWrite();
201 }
202
Ed Tanous26ccae32023-02-16 10:28:44 -0800203 void close(std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700204 {
205 ws.async_close(
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200206 {boost::beast::websocket::close_code::normal, msg},
Ed Tanous5e7e2dc2023-02-16 10:37:01 -0800207 [self(shared_from_this())](const boost::system::error_code& ec) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700208 if (ec == boost::asio::error::operation_aborted)
209 {
210 return;
211 }
212 if (ec)
213 {
214 BMCWEB_LOG_ERROR << "Error closing websocket " << ec;
215 return;
216 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700217 });
218 }
219
220 void acceptDone()
221 {
222 BMCWEB_LOG_DEBUG << "Websocket accepted connection";
223
224 if (openHandler)
225 {
zhanghch0577726382021-10-21 14:07:57 +0800226 openHandler(*this);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700227 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800228 doRead();
229 }
230
231 void deferRead() override
232 {
233 readingDefered = true;
234
235 // If we're not actively reading, we need to take ownership of
236 // ourselves for a small portion of time, do that, and clear when we
237 // resume.
238 selfOwned = shared_from_this();
239 }
240
241 void resumeRead() override
242 {
243 readingDefered = false;
244 doRead();
245
246 // No longer need to keep ourselves alive now that read is active.
247 selfOwned.reset();
Ed Tanous1abe55e2018-09-05 08:30:59 -0700248 }
249
250 void doRead()
251 {
Ed Tanous863c1c22022-02-21 21:33:06 -0800252 if (readingDefered)
253 {
254 return;
255 }
256 ws.async_read(inBuffer, [this, self(shared_from_this())](
257 const boost::beast::error_code& ec,
258 size_t bytesRead) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700259 if (ec)
260 {
261 if (ec != boost::beast::websocket::error::closed)
262 {
263 BMCWEB_LOG_ERROR << "doRead error " << ec;
264 }
265 if (closeHandler)
266 {
Ed Tanous079360a2022-06-29 10:05:19 -0700267 std::string reason{ws.reason().reason.c_str()};
268 closeHandler(*this, reason);
Ed Tanous002d39b2022-05-31 08:59:27 -0700269 }
270 return;
271 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800272
273 handleMessage(bytesRead);
Ed Tanous002d39b2022-05-31 08:59:27 -0700274 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700275 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700276 void doWrite()
277 {
278 // If we're already doing a write, ignore the request, it will be picked
279 // up when the current write is complete
280 if (doingWrite)
281 {
282 return;
283 }
284
Ed Tanous863c1c22022-02-21 21:33:06 -0800285 if (outBuffer.size() == 0)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700286 {
287 // Done for now
288 return;
289 }
290 doingWrite = true;
Ed Tanous863c1c22022-02-21 21:33:06 -0800291 ws.async_write(outBuffer.data(), [this, self(shared_from_this())](
292 const boost::beast::error_code& ec,
293 size_t bytesSent) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700294 doingWrite = false;
Ed Tanous863c1c22022-02-21 21:33:06 -0800295 outBuffer.consume(bytesSent);
Ed Tanous002d39b2022-05-31 08:59:27 -0700296 if (ec == boost::beast::websocket::error::closed)
297 {
298 // Do nothing here. doRead handler will call the
299 // closeHandler.
300 close("Write error");
301 return;
302 }
303 if (ec)
304 {
305 BMCWEB_LOG_ERROR << "Error in ws.async_write " << ec;
306 return;
307 }
308 doWrite();
309 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700310 }
311
312 private:
Ed Tanous863c1c22022-02-21 21:33:06 -0800313 void handleMessage(size_t bytesRead)
314 {
315 if (messageExHandler)
316 {
317 // Note, because of the interactions with the read buffers,
318 // this message handler overrides the normal message handler
319 messageExHandler(*this, inString, MessageType::Binary,
320 [this, self(shared_from_this()), bytesRead]() {
321 if (self == nullptr)
322 {
323 return;
324 }
325
326 inBuffer.consume(bytesRead);
327 inString.clear();
328
329 doRead();
330 });
331 return;
332 }
333
334 if (messageHandler)
335 {
336 messageHandler(*this, inString, ws.got_text());
337 }
338 inBuffer.consume(bytesRead);
339 inString.clear();
340 doRead();
341 }
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