blob: 6e5d1edf6bb8661929eb9ab91b3c71bc7e56c2f1 [file] [log] [blame]
Ed Tanous40e9b922024-09-10 13:50:16 -07001// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright OpenBMC Authors
Ed Tanous7045c8d2017-04-03 10:04:37 -07003#pragma once
Ed Tanous3ccb3ad2023-01-13 17:40:03 -08004#include "async_resp.hpp"
Ed Tanousb2896142024-01-31 15:25:47 -08005#include "http_body.hpp"
Ed Tanous04e438c2020-10-03 08:06:26 -07006#include "http_request.hpp"
Gunnar Mills1214b7e2020-06-04 10:11:30 -05007
Ed Tanous609145a2018-09-05 16:27:36 -07008#include <boost/asio/buffer.hpp>
Lei YUad6dd392024-09-12 11:07:23 +00009#include <boost/asio/ssl/error.hpp>
Ed Tanous863c1c22022-02-21 21:33:06 -080010#include <boost/beast/core/multi_buffer.hpp>
Ed Tanous1b0044b2018-08-03 14:30:05 -070011#include <boost/beast/websocket.hpp>
Ed Tanous8db83742024-04-13 09:11:15 -070012#include <boost/beast/websocket/ssl.hpp>
Ed Tanous1abe55e2018-09-05 08:30:59 -070013
Gunnar Mills1214b7e2020-06-04 10:11:30 -050014#include <array>
15#include <functional>
Ed Tanous1b0044b2018-08-03 14:30:05 -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 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 Tanous9eb808c2022-01-25 10:19:23 -080042 virtual void close(std::string_view msg = "quit") = 0;
Ed Tanous863c1c22022-02-21 21:33:06 -080043 virtual void deferRead() = 0;
44 virtual void resumeRead() = 0;
Ed Tanous2c70f802020-09-28 14:29:23 -070045 virtual boost::asio::io_context& getIoContext() = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070046 virtual ~Connection() = default;
Ninad Palsule052bcbf2023-05-30 11:10:58 -050047 virtual boost::urls::url_view url() = 0;
Ed Tanous7045c8d2017-04-03 10:04:37 -070048};
49
Gunnar Mills1214b7e2020-06-04 10:11:30 -050050template <typename Adaptor>
51class ConnectionImpl : public Connection
Ed Tanous1abe55e2018-09-05 08:30:59 -070052{
Ed Tanous5ebb9d32023-02-27 18:20:47 -080053 using self_t = ConnectionImpl<Adaptor>;
54
Ed Tanous1abe55e2018-09-05 08:30:59 -070055 public:
56 ConnectionImpl(
Ed Tanous5ebb9d32023-02-27 18:20:47 -080057 const boost::urls::url_view& urlViewIn,
58 const std::shared_ptr<persistent_data::UserSession>& sessionIn,
Ninad Palsule052bcbf2023-05-30 11:10:58 -050059 Adaptor adaptorIn, 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) :
Patrick Williamsbd79bce2024-08-16 15:22:20 -040068 uri(urlViewIn), ws(std::move(adaptorIn)), inBuffer(inString, 131088),
Ed Tanous8a592812022-06-04 09:06:59 -070069 openHandler(std::move(openHandlerIn)),
70 messageHandler(std::move(messageHandlerIn)),
Ed Tanous863c1c22022-02-21 21:33:06 -080071 messageExHandler(std::move(messageExHandlerIn)),
Ed Tanous8a592812022-06-04 09:06:59 -070072 closeHandler(std::move(closeHandlerIn)),
Ed Tanous5ebb9d32023-02-27 18:20:47 -080073 errorHandler(std::move(errorHandlerIn)), session(sessionIn)
Ed Tanous1abe55e2018-09-05 08:30:59 -070074 {
dhineskumare02bdd962021-07-08 16:06:49 +053075 /* Turn on the timeouts on websocket stream to server role */
76 ws.set_option(boost::beast::websocket::stream_base::timeout::suggested(
77 boost::beast::role_type::server));
Ed Tanous62598e32023-07-17 17:06:25 -070078 BMCWEB_LOG_DEBUG("Creating new connection {}", logPtr(this));
Ed Tanous7045c8d2017-04-03 10:04:37 -070079 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070080
Ed Tanous2c70f802020-09-28 14:29:23 -070081 boost::asio::io_context& getIoContext() override
Ed Tanous1abe55e2018-09-05 08:30:59 -070082 {
Ed Tanous271584a2019-07-09 16:24:22 -070083 return static_cast<boost::asio::io_context&>(
84 ws.get_executor().context());
Ed Tanous911ac312017-08-15 09:37:42 -070085 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070086
Ed Tanous5ebb9d32023-02-27 18:20:47 -080087 void start(const crow::Request& req)
Ed Tanous1abe55e2018-09-05 08:30:59 -070088 {
Ed Tanous62598e32023-07-17 17:06:25 -070089 BMCWEB_LOG_DEBUG("starting connection {}", logPtr(this));
Ed Tanous7045c8d2017-04-03 10:04:37 -070090
Ed Tanousfe5b2162019-05-22 14:28:16 -070091 using bf = boost::beast::http::field;
Myung Bae1873a042024-04-01 09:27:39 -050092 std::string protocolHeader{
93 req.getHeaderValue(bf::sec_websocket_protocol)};
Ed Tanous7045c8d2017-04-03 10:04:37 -070094
Ed Tanousd4d77e32020-08-18 00:07:28 -070095 ws.set_option(boost::beast::websocket::stream_base::decorator(
Ed Tanous5ebb9d32023-02-27 18:20:47 -080096 [session{session},
97 protocolHeader](boost::beast::websocket::response_type& m) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -040098 if constexpr (!BMCWEB_INSECURE_DISABLE_CSRF)
James Feistf8aa3d22020-04-08 18:32:33 -070099 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400100 if (session != nullptr)
Ed Tanous83328312024-05-09 15:48:09 -0700101 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400102 // use protocol for csrf checking
103 if (session->cookieAuth &&
104 !bmcweb::constantTimeStringCompare(
105 protocolHeader, session->csrfToken))
106 {
107 BMCWEB_LOG_ERROR("Websocket CSRF error");
108 m.result(boost::beast::http::status::unauthorized);
109 return;
110 }
Ed Tanous83328312024-05-09 15:48:09 -0700111 }
James Feistf8aa3d22020-04-08 18:32:33 -0700112 }
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400113 if (!protocolHeader.empty())
114 {
115 m.insert(bf::sec_websocket_protocol, protocolHeader);
116 }
Ed Tanousfe5b2162019-05-22 14:28:16 -0700117
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400118 m.insert(bf::strict_transport_security,
119 "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 Tanousb2896142024-01-31 15:25:47 -0800131 using Body = boost::beast::http::request<bmcweb::HttpBody>;
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) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400163 std::shared_ptr<Connection> self = weak.lock();
164 if (!self)
165 {
166 BMCWEB_LOG_ERROR("Connection went away");
167 return;
168 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800169
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400170 // Call the done handler regardless of whether we
171 // errored, but before we close things out
172 onDone();
Ed Tanous863c1c22022-02-21 21:33:06 -0800173
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400174 if (ec)
175 {
176 BMCWEB_LOG_ERROR("Error in ws.async_write {}",
177 ec);
178 self->close("write error");
179 }
180 });
Ed Tanous863c1c22022-02-21 21:33:06 -0800181 }
182
Ed Tanous26ccae32023-02-16 10:28:44 -0800183 void sendText(std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700184 {
185 ws.text(true);
Ed Tanous863c1c22022-02-21 21:33:06 -0800186 outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()),
187 boost::asio::buffer(msg)));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700188 doWrite();
189 }
190
Ed Tanous26ccae32023-02-16 10:28:44 -0800191 void close(std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700192 {
193 ws.async_close(
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200194 {boost::beast::websocket::close_code::normal, msg},
Ed Tanous5e7e2dc2023-02-16 10:37:01 -0800195 [self(shared_from_this())](const boost::system::error_code& ec) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400196 if (ec == boost::asio::error::operation_aborted)
197 {
198 return;
199 }
200 if (ec)
201 {
202 BMCWEB_LOG_ERROR("Error closing websocket {}", ec);
203 return;
204 }
205 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700206 }
207
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500208 boost::urls::url_view url() override
209 {
210 return uri;
211 }
212
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800213 void acceptDone(const std::shared_ptr<Connection>& /*self*/,
Ed Tanous52e31622024-01-23 16:31:11 -0800214 const std::unique_ptr<
Ed Tanousb2896142024-01-31 15:25:47 -0800215 boost::beast::http::request<bmcweb::HttpBody>>& /*req*/,
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800216 const boost::system::error_code& ec)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700217 {
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800218 if (ec)
219 {
220 BMCWEB_LOG_ERROR("Error in ws.async_accept {}", ec);
221 return;
222 }
Ed Tanous62598e32023-07-17 17:06:25 -0700223 BMCWEB_LOG_DEBUG("Websocket accepted connection");
Ed Tanous1abe55e2018-09-05 08:30:59 -0700224
225 if (openHandler)
226 {
zhanghch0577726382021-10-21 14:07:57 +0800227 openHandler(*this);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700228 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800229 doRead();
230 }
231
232 void deferRead() override
233 {
234 readingDefered = true;
235
236 // If we're not actively reading, we need to take ownership of
237 // ourselves for a small portion of time, do that, and clear when we
238 // resume.
239 selfOwned = shared_from_this();
240 }
241
242 void resumeRead() override
243 {
244 readingDefered = false;
245 doRead();
246
247 // No longer need to keep ourselves alive now that read is active.
248 selfOwned.reset();
Ed Tanous1abe55e2018-09-05 08:30:59 -0700249 }
250
251 void doRead()
252 {
Ed Tanous863c1c22022-02-21 21:33:06 -0800253 if (readingDefered)
254 {
255 return;
256 }
257 ws.async_read(inBuffer, [this, self(shared_from_this())](
258 const boost::beast::error_code& ec,
259 size_t bytesRead) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700260 if (ec)
261 {
Lei YUad6dd392024-09-12 11:07:23 +0000262 if (ec != boost::beast::websocket::error::closed &&
263 ec != boost::asio::error::eof &&
264 ec != boost::asio::ssl::error::stream_truncated)
Ed Tanous002d39b2022-05-31 08:59:27 -0700265 {
Ed Tanous62598e32023-07-17 17:06:25 -0700266 BMCWEB_LOG_ERROR("doRead error {}", ec);
Ed Tanous002d39b2022-05-31 08:59:27 -0700267 }
268 if (closeHandler)
269 {
Ed Tanous079360a2022-06-29 10:05:19 -0700270 std::string reason{ws.reason().reason.c_str()};
271 closeHandler(*this, reason);
Ed Tanous002d39b2022-05-31 08:59:27 -0700272 }
273 return;
274 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800275
276 handleMessage(bytesRead);
Ed Tanous002d39b2022-05-31 08:59:27 -0700277 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700278 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700279 void doWrite()
280 {
281 // If we're already doing a write, ignore the request, it will be picked
282 // up when the current write is complete
283 if (doingWrite)
284 {
285 return;
286 }
287
Ed Tanous863c1c22022-02-21 21:33:06 -0800288 if (outBuffer.size() == 0)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700289 {
290 // Done for now
291 return;
292 }
293 doingWrite = true;
Ed Tanous863c1c22022-02-21 21:33:06 -0800294 ws.async_write(outBuffer.data(), [this, self(shared_from_this())](
295 const boost::beast::error_code& ec,
296 size_t bytesSent) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700297 doingWrite = false;
Ed Tanous863c1c22022-02-21 21:33:06 -0800298 outBuffer.consume(bytesSent);
Ed Tanous002d39b2022-05-31 08:59:27 -0700299 if (ec == boost::beast::websocket::error::closed)
300 {
301 // Do nothing here. doRead handler will call the
302 // closeHandler.
303 close("Write error");
304 return;
305 }
306 if (ec)
307 {
Ed Tanous62598e32023-07-17 17:06:25 -0700308 BMCWEB_LOG_ERROR("Error in ws.async_write {}", ec);
Ed Tanous002d39b2022-05-31 08:59:27 -0700309 return;
310 }
311 doWrite();
312 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700313 }
314
315 private:
Ed Tanous863c1c22022-02-21 21:33:06 -0800316 void handleMessage(size_t bytesRead)
317 {
318 if (messageExHandler)
319 {
320 // Note, because of the interactions with the read buffers,
321 // this message handler overrides the normal message handler
322 messageExHandler(*this, inString, MessageType::Binary,
323 [this, self(shared_from_this()), bytesRead]() {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400324 if (self == nullptr)
325 {
326 return;
327 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800328
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400329 inBuffer.consume(bytesRead);
330 inString.clear();
Ed Tanous863c1c22022-02-21 21:33:06 -0800331
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400332 doRead();
333 });
Ed Tanous863c1c22022-02-21 21:33:06 -0800334 return;
335 }
336
337 if (messageHandler)
338 {
339 messageHandler(*this, inString, ws.got_text());
340 }
341 inBuffer.consume(bytesRead);
342 inString.clear();
343 doRead();
344 }
345
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500346 boost::urls::url uri;
347
Ed Tanous2aee6ca2021-02-01 09:52:17 -0800348 boost::beast::websocket::stream<Adaptor, false> ws;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700349
Ed Tanous863c1c22022-02-21 21:33:06 -0800350 bool readingDefered = false;
Ed Tanous609145a2018-09-05 16:27:36 -0700351 std::string inString;
352 boost::asio::dynamic_string_buffer<std::string::value_type,
353 std::string::traits_type,
354 std::string::allocator_type>
355 inBuffer;
Ed Tanous863c1c22022-02-21 21:33:06 -0800356
357 boost::beast::multi_buffer outBuffer;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700358 bool doingWrite = false;
359
zhanghch0577726382021-10-21 14:07:57 +0800360 std::function<void(Connection&)> openHandler;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700361 std::function<void(Connection&, const std::string&, bool)> messageHandler;
Ed Tanous863c1c22022-02-21 21:33:06 -0800362 std::function<void(crow::websocket::Connection&, std::string_view,
363 crow::websocket::MessageType type,
364 std::function<void()>&& whenComplete)>
365 messageExHandler;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700366 std::function<void(Connection&, const std::string&)> closeHandler;
367 std::function<void(Connection&)> errorHandler;
Ed Tanous52cc1122020-07-18 13:51:21 -0700368 std::shared_ptr<persistent_data::UserSession> session;
Ed Tanous863c1c22022-02-21 21:33:06 -0800369
370 std::shared_ptr<Connection> selfOwned;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700371};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700372} // namespace websocket
373} // namespace crow