blob: 3aa8554e1c7717c1f3b3c3dfcbb61fde24ea2557 [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
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 Tanous9eb808c2022-01-25 10:19:23 -080039 virtual void sendText(std::string_view msg) = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070040 virtual void sendText(std::string&& msg) = 0;
Ed Tanous9eb808c2022-01-25 10:19:23 -080041 virtual void close(std::string_view msg = "quit") = 0;
Ed Tanous2c70f802020-09-28 14:29:23 -070042 virtual boost::asio::io_context& getIoContext() = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070043 virtual ~Connection() = default;
Ed Tanous7045c8d2017-04-03 10:04:37 -070044
Ed Tanous1abe55e2018-09-05 08:30:59 -070045 void userdata(void* u)
46 {
47 userdataPtr = u;
48 }
49 void* userdata()
50 {
51 return userdataPtr;
52 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070053
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +010054 const std::string& getUserName() const
55 {
56 return userName;
57 }
58
Jan Sowinskiee52ae12020-01-09 16:28:32 +000059 boost::beast::http::request<boost::beast::http::string_body> req;
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020060 crow::Response res;
Ed Tanous911ac312017-08-15 09:37:42 -070061
Ed Tanous1abe55e2018-09-05 08:30:59 -070062 private:
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +010063 std::string userName{};
Ed Tanous1abe55e2018-09-05 08:30:59 -070064 void* userdataPtr;
Ed Tanous7045c8d2017-04-03 10:04:37 -070065};
66
Gunnar Mills1214b7e2020-06-04 10:11:30 -050067template <typename Adaptor>
68class ConnectionImpl : public Connection
Ed Tanous1abe55e2018-09-05 08:30:59 -070069{
70 public:
71 ConnectionImpl(
Jan Sowinskiee52ae12020-01-09 16:28:32 +000072 const crow::Request& reqIn, Adaptor adaptorIn,
Ed Tanous8a592812022-06-04 09:06:59 -070073 std::function<void(Connection&)> openHandlerIn,
Ed Tanous1abe55e2018-09-05 08:30:59 -070074 std::function<void(Connection&, const std::string&, bool)>
Ed Tanous8a592812022-06-04 09:06:59 -070075 messageHandlerIn,
76 std::function<void(Connection&, const std::string&)> closeHandlerIn,
77 std::function<void(Connection&)> errorHandlerIn) :
Nan Zhou93c02022022-02-24 18:21:07 -080078 Connection(reqIn, reqIn.session == nullptr ? std::string{}
79 : reqIn.session->username),
Ed Tanouse05aec52022-01-25 10:28:56 -080080 ws(std::move(adaptorIn)), inBuffer(inString, 131088),
Ed Tanous8a592812022-06-04 09:06:59 -070081 openHandler(std::move(openHandlerIn)),
82 messageHandler(std::move(messageHandlerIn)),
83 closeHandler(std::move(closeHandlerIn)),
84 errorHandler(std::move(errorHandlerIn)), session(reqIn.session)
Ed Tanous1abe55e2018-09-05 08:30:59 -070085 {
dhineskumare02bdd962021-07-08 16:06:49 +053086 /* Turn on the timeouts on websocket stream to server role */
87 ws.set_option(boost::beast::websocket::stream_base::timeout::suggested(
88 boost::beast::role_type::server));
Ed Tanous1abe55e2018-09-05 08:30:59 -070089 BMCWEB_LOG_DEBUG << "Creating new connection " << this;
Ed Tanous7045c8d2017-04-03 10:04:37 -070090 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070091
Ed Tanous2c70f802020-09-28 14:29:23 -070092 boost::asio::io_context& getIoContext() override
Ed Tanous1abe55e2018-09-05 08:30:59 -070093 {
Ed Tanous271584a2019-07-09 16:24:22 -070094 return static_cast<boost::asio::io_context&>(
95 ws.get_executor().context());
Ed Tanous911ac312017-08-15 09:37:42 -070096 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070097
Ed Tanous1abe55e2018-09-05 08:30:59 -070098 void start()
99 {
100 BMCWEB_LOG_DEBUG << "starting connection " << this;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700101
Ed Tanousfe5b2162019-05-22 14:28:16 -0700102 using bf = boost::beast::http::field;
103
Jan Sowinskiee52ae12020-01-09 16:28:32 +0000104 std::string_view protocol = req[bf::sec_websocket_protocol];
Ed Tanous7045c8d2017-04-03 10:04:37 -0700105
Ed Tanousd4d77e32020-08-18 00:07:28 -0700106 ws.set_option(boost::beast::websocket::stream_base::decorator(
James Feistf8aa3d22020-04-08 18:32:33 -0700107 [session{session}, protocol{std::string(protocol)}](
Ed Tanous1abe55e2018-09-05 08:30:59 -0700108 boost::beast::websocket::response_type& m) {
James Feistf8aa3d22020-04-08 18:32:33 -0700109
110#ifndef BMCWEB_INSECURE_DISABLE_CSRF_PREVENTION
Ed Tanous002d39b2022-05-31 08:59:27 -0700111 if (session != nullptr)
112 {
113 // use protocol for csrf checking
114 if (session->cookieAuth &&
115 !crow::utility::constantTimeStringCompare(
116 protocol, session->csrfToken))
James Feistf8aa3d22020-04-08 18:32:33 -0700117 {
Ed Tanous002d39b2022-05-31 08:59:27 -0700118 BMCWEB_LOG_ERROR << "Websocket CSRF error";
119 m.result(boost::beast::http::status::unauthorized);
120 return;
James Feistf8aa3d22020-04-08 18:32:33 -0700121 }
Ed Tanous002d39b2022-05-31 08:59:27 -0700122 }
James Feistf8aa3d22020-04-08 18:32:33 -0700123#endif
Ed Tanous002d39b2022-05-31 08:59:27 -0700124 if (!protocol.empty())
125 {
126 m.insert(bf::sec_websocket_protocol, protocol);
127 }
Ed Tanousfe5b2162019-05-22 14:28:16 -0700128
Ed Tanous002d39b2022-05-31 08:59:27 -0700129 m.insert(bf::strict_transport_security, "max-age=31536000; "
130 "includeSubdomains; "
131 "preload");
132 m.insert(bf::pragma, "no-cache");
133 m.insert(bf::cache_control, "no-Store,no-Cache");
134 m.insert("Content-Security-Policy", "default-src 'self'");
135 m.insert("X-XSS-Protection", "1; "
136 "mode=block");
137 m.insert("X-Content-Type-Options", "nosniff");
138 }));
Ed Tanousd4d77e32020-08-18 00:07:28 -0700139
140 // Perform the websocket upgrade
141 ws.async_accept(req, [this, self(shared_from_this())](
142 boost::system::error_code ec) {
143 if (ec)
144 {
145 BMCWEB_LOG_ERROR << "Error in ws.async_accept " << ec;
146 return;
147 }
148 acceptDone();
149 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700150 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700151
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500152 void sendBinary(const std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700153 {
154 ws.binary(true);
155 outBuffer.emplace_back(msg);
156 doWrite();
157 }
158
159 void sendBinary(std::string&& msg) override
160 {
161 ws.binary(true);
162 outBuffer.emplace_back(std::move(msg));
163 doWrite();
164 }
165
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500166 void sendText(const std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700167 {
168 ws.text(true);
169 outBuffer.emplace_back(msg);
170 doWrite();
171 }
172
173 void sendText(std::string&& msg) override
174 {
175 ws.text(true);
176 outBuffer.emplace_back(std::move(msg));
177 doWrite();
178 }
179
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500180 void close(const std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700181 {
182 ws.async_close(
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200183 {boost::beast::websocket::close_code::normal, msg},
Ed Tanous43b761d2019-02-13 20:10:56 -0800184 [self(shared_from_this())](boost::system::error_code ec) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700185 if (ec == boost::asio::error::operation_aborted)
186 {
187 return;
188 }
189 if (ec)
190 {
191 BMCWEB_LOG_ERROR << "Error closing websocket " << ec;
192 return;
193 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700194 });
195 }
196
197 void acceptDone()
198 {
199 BMCWEB_LOG_DEBUG << "Websocket accepted connection";
200
Nan Zhou72374eb2022-01-27 17:06:51 -0800201 doRead();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200202
Ed Tanous1abe55e2018-09-05 08:30:59 -0700203 if (openHandler)
204 {
zhanghch0577726382021-10-21 14:07:57 +0800205 openHandler(*this);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700206 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700207 }
208
209 void doRead()
210 {
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500211 ws.async_read(inBuffer,
212 [this, self(shared_from_this())](
Ed Tanous81ce6092020-12-17 16:54:55 +0000213 boost::beast::error_code ec, std::size_t bytesRead) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700214 if (ec)
215 {
216 if (ec != boost::beast::websocket::error::closed)
217 {
218 BMCWEB_LOG_ERROR << "doRead error " << ec;
219 }
220 if (closeHandler)
221 {
222 std::string_view reason = ws.reason().reason;
223 closeHandler(*this, std::string(reason));
224 }
225 return;
226 }
227 if (messageHandler)
228 {
229 messageHandler(*this, inString, ws.got_text());
230 }
231 inBuffer.consume(bytesRead);
232 inString.clear();
233 doRead();
234 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700235 }
236
237 void doWrite()
238 {
239 // If we're already doing a write, ignore the request, it will be picked
240 // up when the current write is complete
241 if (doingWrite)
242 {
243 return;
244 }
245
246 if (outBuffer.empty())
247 {
248 // Done for now
249 return;
250 }
251 doingWrite = true;
Ed Tanouscb13a392020-07-25 19:02:03 +0000252 ws.async_write(boost::asio::buffer(outBuffer.front()),
253 [this, self(shared_from_this())](
254 boost::beast::error_code ec, std::size_t) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700255 doingWrite = false;
256 outBuffer.erase(outBuffer.begin());
257 if (ec == boost::beast::websocket::error::closed)
258 {
259 // Do nothing here. doRead handler will call the
260 // closeHandler.
261 close("Write error");
262 return;
263 }
264 if (ec)
265 {
266 BMCWEB_LOG_ERROR << "Error in ws.async_write " << ec;
267 return;
268 }
269 doWrite();
270 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700271 }
272
273 private:
Ed Tanous2aee6ca2021-02-01 09:52:17 -0800274 boost::beast::websocket::stream<Adaptor, false> ws;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700275
Ed Tanous609145a2018-09-05 16:27:36 -0700276 std::string inString;
277 boost::asio::dynamic_string_buffer<std::string::value_type,
278 std::string::traits_type,
279 std::string::allocator_type>
280 inBuffer;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700281 std::vector<std::string> outBuffer;
282 bool doingWrite = false;
283
zhanghch0577726382021-10-21 14:07:57 +0800284 std::function<void(Connection&)> openHandler;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700285 std::function<void(Connection&, const std::string&, bool)> messageHandler;
286 std::function<void(Connection&, const std::string&)> closeHandler;
287 std::function<void(Connection&)> errorHandler;
Ed Tanous52cc1122020-07-18 13:51:21 -0700288 std::shared_ptr<persistent_data::UserSession> session;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700289};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700290} // namespace websocket
291} // namespace crow