blob: 06f35d1fbcda798f81cf43d6656b19a5b14f4f94 [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 Tanous911ac312017-08-15 09:37:42 -07005#include <boost/algorithm/string/predicate.hpp>
Ed Tanous609145a2018-09-05 16:27:36 -07006#include <boost/asio/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 Tanous1abe55e2018-09-05 08:30:59 -070021struct Connection : std::enable_shared_from_this<Connection>
22{
23 public:
Jan Sowinskiee52ae12020-01-09 16:28:32 +000024 explicit Connection(const crow::Request& reqIn) :
Ed Tanous23a21a12020-07-25 04:45:05 +000025 req(reqIn.req), userdataPtr(nullptr)
26 {}
Ed Tanous911ac312017-08-15 09:37:42 -070027
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +010028 explicit Connection(const crow::Request& reqIn, std::string user) :
Ed Tanous23a21a12020-07-25 04:45:05 +000029 req(reqIn.req), userName{std::move(user)}, userdataPtr(nullptr)
30 {}
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +010031
Adriana Kobylakae29b8c2019-04-24 11:19:18 -050032 virtual void sendBinary(const std::string_view msg) = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070033 virtual void sendBinary(std::string&& msg) = 0;
Adriana Kobylakae29b8c2019-04-24 11:19:18 -050034 virtual void sendText(const std::string_view msg) = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070035 virtual void sendText(std::string&& msg) = 0;
Adriana Kobylakae29b8c2019-04-24 11:19:18 -050036 virtual void close(const std::string_view msg = "quit") = 0;
Ed Tanous2c70f802020-09-28 14:29:23 -070037 virtual boost::asio::io_context& getIoContext() = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070038 virtual ~Connection() = default;
Ed Tanous7045c8d2017-04-03 10:04:37 -070039
Ed Tanous1abe55e2018-09-05 08:30:59 -070040 void userdata(void* u)
41 {
42 userdataPtr = u;
43 }
44 void* userdata()
45 {
46 return userdataPtr;
47 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070048
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +010049 const std::string& getUserName() const
50 {
51 return userName;
52 }
53
Jan Sowinskiee52ae12020-01-09 16:28:32 +000054 boost::beast::http::request<boost::beast::http::string_body> req;
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020055 crow::Response res;
Ed Tanous911ac312017-08-15 09:37:42 -070056
Ed Tanous1abe55e2018-09-05 08:30:59 -070057 private:
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +010058 std::string userName{};
Ed Tanous1abe55e2018-09-05 08:30:59 -070059 void* userdataPtr;
Ed Tanous7045c8d2017-04-03 10:04:37 -070060};
61
Gunnar Mills1214b7e2020-06-04 10:11:30 -050062template <typename Adaptor>
63class ConnectionImpl : public Connection
Ed Tanous1abe55e2018-09-05 08:30:59 -070064{
65 public:
66 ConnectionImpl(
Jan Sowinskiee52ae12020-01-09 16:28:32 +000067 const crow::Request& reqIn, Adaptor adaptorIn,
zhanghch050f3d3a02021-10-21 14:07:57 +080068 std::function<void(Connection&)> openHandler,
Ed Tanous1abe55e2018-09-05 08:30:59 -070069 std::function<void(Connection&, const std::string&, bool)>
Ed Tanous81ce6092020-12-17 16:54:55 +000070 messageHandler,
71 std::function<void(Connection&, const std::string&)> closeHandler,
72 std::function<void(Connection&)> errorHandler) :
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +010073 Connection(reqIn, reqIn.session->username),
Adriana Kobylak69664cf2019-03-12 10:49:48 -050074 ws(std::move(adaptorIn)), inString(), inBuffer(inString, 131088),
Ed Tanous81ce6092020-12-17 16:54:55 +000075 openHandler(std::move(openHandler)),
76 messageHandler(std::move(messageHandler)),
77 closeHandler(std::move(closeHandler)),
78 errorHandler(std::move(errorHandler)), session(reqIn.session)
Ed Tanous1abe55e2018-09-05 08:30:59 -070079 {
dhineskumare02bdd962021-07-08 16:06:49 +053080 /* Turn on the timeouts on websocket stream to server role */
81 ws.set_option(boost::beast::websocket::stream_base::timeout::suggested(
82 boost::beast::role_type::server));
Ed Tanous1abe55e2018-09-05 08:30:59 -070083 BMCWEB_LOG_DEBUG << "Creating new connection " << this;
Ed Tanous7045c8d2017-04-03 10:04:37 -070084 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070085
Ed Tanous2c70f802020-09-28 14:29:23 -070086 boost::asio::io_context& getIoContext() override
Ed Tanous1abe55e2018-09-05 08:30:59 -070087 {
Ed Tanous271584a2019-07-09 16:24:22 -070088 return static_cast<boost::asio::io_context&>(
89 ws.get_executor().context());
Ed Tanous911ac312017-08-15 09:37:42 -070090 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070091
Ed Tanous1abe55e2018-09-05 08:30:59 -070092 void start()
93 {
94 BMCWEB_LOG_DEBUG << "starting connection " << this;
Ed Tanous7045c8d2017-04-03 10:04:37 -070095
Ed Tanousfe5b2162019-05-22 14:28:16 -070096 using bf = boost::beast::http::field;
97
Jan Sowinskiee52ae12020-01-09 16:28:32 +000098 std::string_view protocol = req[bf::sec_websocket_protocol];
Ed Tanous7045c8d2017-04-03 10:04:37 -070099
Ed Tanousd4d77e32020-08-18 00:07:28 -0700100 ws.set_option(boost::beast::websocket::stream_base::decorator(
James Feistf8aa3d22020-04-08 18:32:33 -0700101 [session{session}, protocol{std::string(protocol)}](
Ed Tanous1abe55e2018-09-05 08:30:59 -0700102 boost::beast::websocket::response_type& m) {
James Feistf8aa3d22020-04-08 18:32:33 -0700103
104#ifndef BMCWEB_INSECURE_DISABLE_CSRF_PREVENTION
Ed Tanousa90daf12021-01-15 14:18:01 -0800105 if (session != nullptr)
James Feistf8aa3d22020-04-08 18:32:33 -0700106 {
Ed Tanousa90daf12021-01-15 14:18:01 -0800107 // use protocol for csrf checking
108 if (session->cookieAuth &&
109 !crow::utility::constantTimeStringCompare(
110 protocol, session->csrfToken))
111 {
112 BMCWEB_LOG_ERROR << "Websocket CSRF error";
113 m.result(boost::beast::http::status::unauthorized);
114 return;
115 }
James Feistf8aa3d22020-04-08 18:32:33 -0700116 }
117#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700118 if (!protocol.empty())
119 {
Ed Tanousfe5b2162019-05-22 14:28:16 -0700120 m.insert(bf::sec_websocket_protocol, protocol);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700121 }
Ed Tanousfe5b2162019-05-22 14:28:16 -0700122
123 m.insert(bf::strict_transport_security, "max-age=31536000; "
124 "includeSubdomains; "
125 "preload");
126 m.insert(bf::pragma, "no-cache");
127 m.insert(bf::cache_control, "no-Store,no-Cache");
128 m.insert("Content-Security-Policy", "default-src 'self'");
129 m.insert("X-XSS-Protection", "1; "
130 "mode=block");
131 m.insert("X-Content-Type-Options", "nosniff");
Ed Tanousd4d77e32020-08-18 00:07:28 -0700132 }));
133
134 // Perform the websocket upgrade
135 ws.async_accept(req, [this, self(shared_from_this())](
136 boost::system::error_code ec) {
137 if (ec)
138 {
139 BMCWEB_LOG_ERROR << "Error in ws.async_accept " << ec;
140 return;
141 }
142 acceptDone();
143 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700144 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700145
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500146 void sendBinary(const std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700147 {
148 ws.binary(true);
149 outBuffer.emplace_back(msg);
150 doWrite();
151 }
152
153 void sendBinary(std::string&& msg) override
154 {
155 ws.binary(true);
156 outBuffer.emplace_back(std::move(msg));
157 doWrite();
158 }
159
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500160 void sendText(const std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700161 {
162 ws.text(true);
163 outBuffer.emplace_back(msg);
164 doWrite();
165 }
166
167 void sendText(std::string&& msg) override
168 {
169 ws.text(true);
170 outBuffer.emplace_back(std::move(msg));
171 doWrite();
172 }
173
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500174 void close(const std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700175 {
176 ws.async_close(
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200177 {boost::beast::websocket::close_code::normal, msg},
Ed Tanous43b761d2019-02-13 20:10:56 -0800178 [self(shared_from_this())](boost::system::error_code ec) {
Ed Tanousceac6f72018-12-02 11:58:47 -0800179 if (ec == boost::asio::error::operation_aborted)
180 {
181 return;
182 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700183 if (ec)
184 {
185 BMCWEB_LOG_ERROR << "Error closing websocket " << ec;
186 return;
187 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700188 });
189 }
190
191 void acceptDone()
192 {
193 BMCWEB_LOG_DEBUG << "Websocket accepted connection";
194
zhanghch0591995f32021-10-20 18:43:55 +0800195 doRead();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200196
Ed Tanous1abe55e2018-09-05 08:30:59 -0700197 if (openHandler)
198 {
zhanghch050f3d3a02021-10-21 14:07:57 +0800199 openHandler(*this);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700200 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700201 }
202
203 void doRead()
204 {
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500205 ws.async_read(inBuffer,
206 [this, self(shared_from_this())](
Ed Tanous81ce6092020-12-17 16:54:55 +0000207 boost::beast::error_code ec, std::size_t bytesRead) {
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500208 if (ec)
209 {
210 if (ec != boost::beast::websocket::error::closed)
211 {
212 BMCWEB_LOG_ERROR << "doRead error " << ec;
213 }
214 if (closeHandler)
215 {
216 std::string_view reason = ws.reason().reason;
217 closeHandler(*this, std::string(reason));
218 }
219 return;
220 }
221 if (messageHandler)
222 {
223 messageHandler(*this, inString, ws.got_text());
224 }
Ed Tanous81ce6092020-12-17 16:54:55 +0000225 inBuffer.consume(bytesRead);
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500226 inString.clear();
227 doRead();
228 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700229 }
230
231 void doWrite()
232 {
233 // If we're already doing a write, ignore the request, it will be picked
234 // up when the current write is complete
235 if (doingWrite)
236 {
237 return;
238 }
239
240 if (outBuffer.empty())
241 {
242 // Done for now
243 return;
244 }
245 doingWrite = true;
Ed Tanouscb13a392020-07-25 19:02:03 +0000246 ws.async_write(boost::asio::buffer(outBuffer.front()),
247 [this, self(shared_from_this())](
248 boost::beast::error_code ec, std::size_t) {
249 doingWrite = false;
250 outBuffer.erase(outBuffer.begin());
251 if (ec == boost::beast::websocket::error::closed)
252 {
253 // Do nothing here. doRead handler will call the
254 // closeHandler.
255 close("Write error");
256 return;
257 }
258 if (ec)
259 {
260 BMCWEB_LOG_ERROR << "Error in ws.async_write "
261 << ec;
262 return;
263 }
264 doWrite();
265 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700266 }
267
268 private:
Ed Tanous2aee6ca2021-02-01 09:52:17 -0800269 boost::beast::websocket::stream<Adaptor, false> ws;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700270
Ed Tanous609145a2018-09-05 16:27:36 -0700271 std::string inString;
272 boost::asio::dynamic_string_buffer<std::string::value_type,
273 std::string::traits_type,
274 std::string::allocator_type>
275 inBuffer;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700276 std::vector<std::string> outBuffer;
277 bool doingWrite = false;
278
zhanghch050f3d3a02021-10-21 14:07:57 +0800279 std::function<void(Connection&)> openHandler;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700280 std::function<void(Connection&, const std::string&, bool)> messageHandler;
281 std::function<void(Connection&, const std::string&)> closeHandler;
282 std::function<void(Connection&)> errorHandler;
Ed Tanous52cc1122020-07-18 13:51:21 -0700283 std::shared_ptr<persistent_data::UserSession> session;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700284};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700285} // namespace websocket
286} // namespace crow