blob: ecaa1d2f9aa126f482b0b099e944f849d57b9202 [file] [log] [blame]
Ed Tanous7045c8d2017-04-03 10:04:37 -07001#pragma once
Ed Tanous04e438c2020-10-03 08:06:26 -07002#include "http_request.hpp"
Gunnar Mills1214b7e2020-06-04 10:11:30 -05003
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +02004#include <async_resp.hpp>
Ed Tanous609145a2018-09-05 16:27:36 -07005#include <boost/asio/buffer.hpp>
Ed Tanous1b0044b2018-08-03 14:30:05 -07006#include <boost/beast/websocket.hpp>
Ed Tanous1abe55e2018-09-05 08:30:59 -07007
Gunnar Mills1214b7e2020-06-04 10:11:30 -05008#include <array>
9#include <functional>
Ed Tanous1b0044b2018-08-03 14:30:05 -070010
11#ifdef BMCWEB_ENABLE_SSL
12#include <boost/beast/websocket/ssl.hpp>
13#endif
Ed Tanous7045c8d2017-04-03 10:04:37 -070014
Ed Tanous1abe55e2018-09-05 08:30:59 -070015namespace crow
16{
17namespace websocket
18{
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020019
Ed Tanous1abe55e2018-09-05 08:30:59 -070020struct Connection : std::enable_shared_from_this<Connection>
21{
22 public:
Jan Sowinskiee52ae12020-01-09 16:28:32 +000023 explicit Connection(const crow::Request& reqIn) :
Ed Tanous23a21a12020-07-25 04:45:05 +000024 req(reqIn.req), userdataPtr(nullptr)
25 {}
Ed Tanous911ac312017-08-15 09:37:42 -070026
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +010027 explicit Connection(const crow::Request& reqIn, std::string user) :
Ed Tanous23a21a12020-07-25 04:45:05 +000028 req(reqIn.req), userName{std::move(user)}, userdataPtr(nullptr)
29 {}
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 Tanous1abe55e2018-09-05 08:30:59 -070037 virtual void sendBinary(std::string&& msg) = 0;
Ed Tanous9eb808c2022-01-25 10:19:23 -080038 virtual void sendText(std::string_view msg) = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070039 virtual void sendText(std::string&& msg) = 0;
Ed Tanous9eb808c2022-01-25 10:19:23 -080040 virtual void close(std::string_view msg = "quit") = 0;
Ed Tanous2c70f802020-09-28 14:29:23 -070041 virtual boost::asio::io_context& getIoContext() = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070042 virtual ~Connection() = default;
Ed Tanous7045c8d2017-04-03 10:04:37 -070043
Ed Tanous1abe55e2018-09-05 08:30:59 -070044 void userdata(void* u)
45 {
46 userdataPtr = u;
47 }
48 void* userdata()
49 {
50 return userdataPtr;
51 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070052
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +010053 const std::string& getUserName() const
54 {
55 return userName;
56 }
57
Jan Sowinskiee52ae12020-01-09 16:28:32 +000058 boost::beast::http::request<boost::beast::http::string_body> req;
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020059 crow::Response res;
Ed Tanous911ac312017-08-15 09:37:42 -070060
Ed Tanous1abe55e2018-09-05 08:30:59 -070061 private:
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +010062 std::string userName{};
Ed Tanous1abe55e2018-09-05 08:30:59 -070063 void* userdataPtr;
Ed Tanous7045c8d2017-04-03 10:04:37 -070064};
65
Gunnar Mills1214b7e2020-06-04 10:11:30 -050066template <typename Adaptor>
67class ConnectionImpl : public Connection
Ed Tanous1abe55e2018-09-05 08:30:59 -070068{
69 public:
70 ConnectionImpl(
Jan Sowinskiee52ae12020-01-09 16:28:32 +000071 const crow::Request& reqIn, Adaptor adaptorIn,
Ed Tanous8a592812022-06-04 09:06:59 -070072 std::function<void(Connection&)> openHandlerIn,
Ed Tanous1abe55e2018-09-05 08:30:59 -070073 std::function<void(Connection&, const std::string&, bool)>
Ed Tanous8a592812022-06-04 09:06:59 -070074 messageHandlerIn,
75 std::function<void(Connection&, const std::string&)> closeHandlerIn,
76 std::function<void(Connection&)> errorHandlerIn) :
Nan Zhou93c02022022-02-24 18:21:07 -080077 Connection(reqIn, reqIn.session == nullptr ? std::string{}
78 : reqIn.session->username),
Ed Tanouse05aec52022-01-25 10:28:56 -080079 ws(std::move(adaptorIn)), inBuffer(inString, 131088),
Ed Tanous8a592812022-06-04 09:06:59 -070080 openHandler(std::move(openHandlerIn)),
81 messageHandler(std::move(messageHandlerIn)),
82 closeHandler(std::move(closeHandlerIn)),
83 errorHandler(std::move(errorHandlerIn)), session(reqIn.session)
Ed Tanous1abe55e2018-09-05 08:30:59 -070084 {
dhineskumare02bdd962021-07-08 16:06:49 +053085 /* Turn on the timeouts on websocket stream to server role */
86 ws.set_option(boost::beast::websocket::stream_base::timeout::suggested(
87 boost::beast::role_type::server));
Ed Tanous1abe55e2018-09-05 08:30:59 -070088 BMCWEB_LOG_DEBUG << "Creating new connection " << this;
Ed Tanous7045c8d2017-04-03 10:04:37 -070089 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070090
Ed Tanous2c70f802020-09-28 14:29:23 -070091 boost::asio::io_context& getIoContext() override
Ed Tanous1abe55e2018-09-05 08:30:59 -070092 {
Ed Tanous271584a2019-07-09 16:24:22 -070093 return static_cast<boost::asio::io_context&>(
94 ws.get_executor().context());
Ed Tanous911ac312017-08-15 09:37:42 -070095 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070096
Ed Tanous1abe55e2018-09-05 08:30:59 -070097 void start()
98 {
99 BMCWEB_LOG_DEBUG << "starting connection " << this;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700100
Ed Tanousfe5b2162019-05-22 14:28:16 -0700101 using bf = boost::beast::http::field;
102
Jan Sowinskiee52ae12020-01-09 16:28:32 +0000103 std::string_view protocol = req[bf::sec_websocket_protocol];
Ed Tanous7045c8d2017-04-03 10:04:37 -0700104
Ed Tanousd4d77e32020-08-18 00:07:28 -0700105 ws.set_option(boost::beast::websocket::stream_base::decorator(
James Feistf8aa3d22020-04-08 18:32:33 -0700106 [session{session}, protocol{std::string(protocol)}](
Ed Tanous1abe55e2018-09-05 08:30:59 -0700107 boost::beast::websocket::response_type& m) {
James Feistf8aa3d22020-04-08 18:32:33 -0700108
109#ifndef BMCWEB_INSECURE_DISABLE_CSRF_PREVENTION
Ed Tanous002d39b2022-05-31 08:59:27 -0700110 if (session != nullptr)
111 {
112 // use protocol for csrf checking
113 if (session->cookieAuth &&
114 !crow::utility::constantTimeStringCompare(
115 protocol, session->csrfToken))
James Feistf8aa3d22020-04-08 18:32:33 -0700116 {
Ed Tanous002d39b2022-05-31 08:59:27 -0700117 BMCWEB_LOG_ERROR << "Websocket CSRF error";
118 m.result(boost::beast::http::status::unauthorized);
119 return;
James Feistf8aa3d22020-04-08 18:32:33 -0700120 }
Ed Tanous002d39b2022-05-31 08:59:27 -0700121 }
James Feistf8aa3d22020-04-08 18:32:33 -0700122#endif
Ed Tanous002d39b2022-05-31 08:59:27 -0700123 if (!protocol.empty())
124 {
125 m.insert(bf::sec_websocket_protocol, protocol);
126 }
Ed Tanousfe5b2162019-05-22 14:28:16 -0700127
Ed Tanous002d39b2022-05-31 08:59:27 -0700128 m.insert(bf::strict_transport_security, "max-age=31536000; "
129 "includeSubdomains; "
130 "preload");
131 m.insert(bf::pragma, "no-cache");
132 m.insert(bf::cache_control, "no-Store,no-Cache");
133 m.insert("Content-Security-Policy", "default-src 'self'");
134 m.insert("X-XSS-Protection", "1; "
135 "mode=block");
136 m.insert("X-Content-Type-Options", "nosniff");
137 }));
Ed Tanousd4d77e32020-08-18 00:07:28 -0700138
139 // Perform the websocket upgrade
140 ws.async_accept(req, [this, self(shared_from_this())](
141 boost::system::error_code ec) {
142 if (ec)
143 {
144 BMCWEB_LOG_ERROR << "Error in ws.async_accept " << ec;
145 return;
146 }
147 acceptDone();
148 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700149 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700150
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500151 void sendBinary(const std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700152 {
153 ws.binary(true);
154 outBuffer.emplace_back(msg);
155 doWrite();
156 }
157
158 void sendBinary(std::string&& msg) override
159 {
160 ws.binary(true);
161 outBuffer.emplace_back(std::move(msg));
162 doWrite();
163 }
164
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500165 void sendText(const std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700166 {
167 ws.text(true);
168 outBuffer.emplace_back(msg);
169 doWrite();
170 }
171
172 void sendText(std::string&& msg) override
173 {
174 ws.text(true);
175 outBuffer.emplace_back(std::move(msg));
176 doWrite();
177 }
178
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500179 void close(const std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700180 {
181 ws.async_close(
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200182 {boost::beast::websocket::close_code::normal, msg},
Ed Tanous43b761d2019-02-13 20:10:56 -0800183 [self(shared_from_this())](boost::system::error_code ec) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700184 if (ec == boost::asio::error::operation_aborted)
185 {
186 return;
187 }
188 if (ec)
189 {
190 BMCWEB_LOG_ERROR << "Error closing websocket " << ec;
191 return;
192 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700193 });
194 }
195
196 void acceptDone()
197 {
198 BMCWEB_LOG_DEBUG << "Websocket accepted connection";
199
Nan Zhou72374eb2022-01-27 17:06:51 -0800200 doRead();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200201
Ed Tanous1abe55e2018-09-05 08:30:59 -0700202 if (openHandler)
203 {
zhanghch0577726382021-10-21 14:07:57 +0800204 openHandler(*this);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700205 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700206 }
207
208 void doRead()
209 {
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500210 ws.async_read(inBuffer,
211 [this, self(shared_from_this())](
Ed Tanous81ce6092020-12-17 16:54:55 +0000212 boost::beast::error_code ec, std::size_t bytesRead) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700213 if (ec)
214 {
215 if (ec != boost::beast::websocket::error::closed)
216 {
217 BMCWEB_LOG_ERROR << "doRead error " << ec;
218 }
219 if (closeHandler)
220 {
221 std::string_view reason = ws.reason().reason;
222 closeHandler(*this, std::string(reason));
223 }
224 return;
225 }
226 if (messageHandler)
227 {
228 messageHandler(*this, inString, ws.got_text());
229 }
230 inBuffer.consume(bytesRead);
231 inString.clear();
232 doRead();
233 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700234 }
235
236 void doWrite()
237 {
238 // If we're already doing a write, ignore the request, it will be picked
239 // up when the current write is complete
240 if (doingWrite)
241 {
242 return;
243 }
244
245 if (outBuffer.empty())
246 {
247 // Done for now
248 return;
249 }
250 doingWrite = true;
Ed Tanouscb13a392020-07-25 19:02:03 +0000251 ws.async_write(boost::asio::buffer(outBuffer.front()),
252 [this, self(shared_from_this())](
253 boost::beast::error_code ec, std::size_t) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700254 doingWrite = false;
255 outBuffer.erase(outBuffer.begin());
256 if (ec == boost::beast::websocket::error::closed)
257 {
258 // Do nothing here. doRead handler will call the
259 // closeHandler.
260 close("Write error");
261 return;
262 }
263 if (ec)
264 {
265 BMCWEB_LOG_ERROR << "Error in ws.async_write " << ec;
266 return;
267 }
268 doWrite();
269 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700270 }
271
272 private:
Ed Tanous2aee6ca2021-02-01 09:52:17 -0800273 boost::beast::websocket::stream<Adaptor, false> ws;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700274
Ed Tanous609145a2018-09-05 16:27:36 -0700275 std::string inString;
276 boost::asio::dynamic_string_buffer<std::string::value_type,
277 std::string::traits_type,
278 std::string::allocator_type>
279 inBuffer;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700280 std::vector<std::string> outBuffer;
281 bool doingWrite = false;
282
zhanghch0577726382021-10-21 14:07:57 +0800283 std::function<void(Connection&)> openHandler;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700284 std::function<void(Connection&, const std::string&, bool)> messageHandler;
285 std::function<void(Connection&, const std::string&)> closeHandler;
286 std::function<void(Connection&)> errorHandler;
Ed Tanous52cc1122020-07-18 13:51:21 -0700287 std::shared_ptr<persistent_data::UserSession> session;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700288};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700289} // namespace websocket
290} // namespace crow