blob: 5e65c993d7f131e47b57664f5c3dc05bc94b163d [file] [log] [blame]
Ed Tanous7045c8d2017-04-03 10:04:37 -07001#pragma once
Gunnar Mills1214b7e2020-06-04 10:11:30 -05002#include "http_request.h"
3
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 Tanousceac6f72018-12-02 11:58:47 -080037 virtual boost::asio::io_context& get_io_context() = 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,
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020068 std::function<void(Connection&, std::shared_ptr<bmcweb::AsyncResp>)>
69 open_handler,
Ed Tanous1abe55e2018-09-05 08:30:59 -070070 std::function<void(Connection&, const std::string&, bool)>
71 message_handler,
72 std::function<void(Connection&, const std::string&)> close_handler,
73 std::function<void(Connection&)> error_handler) :
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +010074 Connection(reqIn, reqIn.session->username),
Adriana Kobylak69664cf2019-03-12 10:49:48 -050075 ws(std::move(adaptorIn)), inString(), inBuffer(inString, 131088),
Ed Tanousceac6f72018-12-02 11:58:47 -080076 openHandler(std::move(open_handler)),
Ed Tanous55c7b7a2018-05-22 15:27:24 -070077 messageHandler(std::move(message_handler)),
78 closeHandler(std::move(close_handler)),
James Feistf8aa3d22020-04-08 18:32:33 -070079 errorHandler(std::move(error_handler)), session(reqIn.session)
Ed Tanous1abe55e2018-09-05 08:30:59 -070080 {
81 BMCWEB_LOG_DEBUG << "Creating new connection " << this;
Ed Tanous7045c8d2017-04-03 10:04:37 -070082 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070083
Ed Tanousceac6f72018-12-02 11:58:47 -080084 boost::asio::io_context& get_io_context() override
Ed Tanous1abe55e2018-09-05 08:30:59 -070085 {
Ed Tanous271584a2019-07-09 16:24:22 -070086 return static_cast<boost::asio::io_context&>(
87 ws.get_executor().context());
Ed Tanous911ac312017-08-15 09:37:42 -070088 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070089
Ed Tanous1abe55e2018-09-05 08:30:59 -070090 void start()
91 {
92 BMCWEB_LOG_DEBUG << "starting connection " << this;
Ed Tanous7045c8d2017-04-03 10:04:37 -070093
Ed Tanousfe5b2162019-05-22 14:28:16 -070094 using bf = boost::beast::http::field;
95
Jan Sowinskiee52ae12020-01-09 16:28:32 +000096 std::string_view protocol = req[bf::sec_websocket_protocol];
Ed Tanous7045c8d2017-04-03 10:04:37 -070097
Ed Tanous1abe55e2018-09-05 08:30:59 -070098 // Perform the websocket upgrade
99 ws.async_accept_ex(
Jan Sowinskiee52ae12020-01-09 16:28:32 +0000100 req,
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
105 // use protocol for csrf checking
106 if (session->cookieAuth &&
107 !crow::utility::constantTimeStringCompare(
108 protocol, session->csrfToken))
109 {
110 BMCWEB_LOG_ERROR << "Websocket CSRF error";
111 m.result(boost::beast::http::status::unauthorized);
112 return;
113 }
114#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700115 if (!protocol.empty())
116 {
Ed Tanousfe5b2162019-05-22 14:28:16 -0700117 m.insert(bf::sec_websocket_protocol, protocol);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700118 }
Ed Tanousfe5b2162019-05-22 14:28:16 -0700119
120 m.insert(bf::strict_transport_security, "max-age=31536000; "
121 "includeSubdomains; "
122 "preload");
123 m.insert(bf::pragma, "no-cache");
124 m.insert(bf::cache_control, "no-Store,no-Cache");
125 m.insert("Content-Security-Policy", "default-src 'self'");
126 m.insert("X-XSS-Protection", "1; "
127 "mode=block");
128 m.insert("X-Content-Type-Options", "nosniff");
Ed Tanous1abe55e2018-09-05 08:30:59 -0700129 },
130 [this, self(shared_from_this())](boost::system::error_code ec) {
131 if (ec)
132 {
133 BMCWEB_LOG_ERROR << "Error in ws.async_accept " << ec;
134 return;
135 }
136 acceptDone();
137 });
138 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700139
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500140 void sendBinary(const std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700141 {
142 ws.binary(true);
143 outBuffer.emplace_back(msg);
144 doWrite();
145 }
146
147 void sendBinary(std::string&& msg) override
148 {
149 ws.binary(true);
150 outBuffer.emplace_back(std::move(msg));
151 doWrite();
152 }
153
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500154 void sendText(const std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700155 {
156 ws.text(true);
157 outBuffer.emplace_back(msg);
158 doWrite();
159 }
160
161 void sendText(std::string&& msg) override
162 {
163 ws.text(true);
164 outBuffer.emplace_back(std::move(msg));
165 doWrite();
166 }
167
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500168 void close(const std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700169 {
170 ws.async_close(
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200171 {boost::beast::websocket::close_code::normal, msg},
Ed Tanous43b761d2019-02-13 20:10:56 -0800172 [self(shared_from_this())](boost::system::error_code ec) {
Ed Tanousceac6f72018-12-02 11:58:47 -0800173 if (ec == boost::asio::error::operation_aborted)
174 {
175 return;
176 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700177 if (ec)
178 {
179 BMCWEB_LOG_ERROR << "Error closing websocket " << ec;
180 return;
181 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700182 });
183 }
184
185 void acceptDone()
186 {
187 BMCWEB_LOG_DEBUG << "Websocket accepted connection";
188
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200189 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(
190 res, [this, self(shared_from_this())]() { doRead(); });
191
192 asyncResp->res.result(boost::beast::http::status::ok);
193
Ed Tanous1abe55e2018-09-05 08:30:59 -0700194 if (openHandler)
195 {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200196 openHandler(*this, asyncResp);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700197 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700198 }
199
200 void doRead()
201 {
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500202 ws.async_read(inBuffer,
203 [this, self(shared_from_this())](
Ed Tanous1abe55e2018-09-05 08:30:59 -0700204 boost::beast::error_code ec, std::size_t bytes_read) {
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500205 if (ec)
206 {
207 if (ec != boost::beast::websocket::error::closed)
208 {
209 BMCWEB_LOG_ERROR << "doRead error " << ec;
210 }
211 if (closeHandler)
212 {
213 std::string_view reason = ws.reason().reason;
214 closeHandler(*this, std::string(reason));
215 }
216 return;
217 }
218 if (messageHandler)
219 {
220 messageHandler(*this, inString, ws.got_text());
221 }
222 inBuffer.consume(bytes_read);
223 inString.clear();
224 doRead();
225 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700226 }
227
228 void doWrite()
229 {
230 // If we're already doing a write, ignore the request, it will be picked
231 // up when the current write is complete
232 if (doingWrite)
233 {
234 return;
235 }
236
237 if (outBuffer.empty())
238 {
239 // Done for now
240 return;
241 }
242 doingWrite = true;
Ed Tanouscb13a392020-07-25 19:02:03 +0000243 ws.async_write(boost::asio::buffer(outBuffer.front()),
244 [this, self(shared_from_this())](
245 boost::beast::error_code ec, std::size_t) {
246 doingWrite = false;
247 outBuffer.erase(outBuffer.begin());
248 if (ec == boost::beast::websocket::error::closed)
249 {
250 // Do nothing here. doRead handler will call the
251 // closeHandler.
252 close("Write error");
253 return;
254 }
255 if (ec)
256 {
257 BMCWEB_LOG_ERROR << "Error in ws.async_write "
258 << ec;
259 return;
260 }
261 doWrite();
262 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700263 }
264
265 private:
Ed Tanousceac6f72018-12-02 11:58:47 -0800266 boost::beast::websocket::stream<Adaptor> ws;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700267
Ed Tanous609145a2018-09-05 16:27:36 -0700268 std::string inString;
269 boost::asio::dynamic_string_buffer<std::string::value_type,
270 std::string::traits_type,
271 std::string::allocator_type>
272 inBuffer;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700273 std::vector<std::string> outBuffer;
274 bool doingWrite = false;
275
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200276 std::function<void(Connection&, std::shared_ptr<bmcweb::AsyncResp>)>
277 openHandler;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700278 std::function<void(Connection&, const std::string&, bool)> messageHandler;
279 std::function<void(Connection&, const std::string&)> closeHandler;
280 std::function<void(Connection&)> errorHandler;
Ed Tanous52cc1122020-07-18 13:51:21 -0700281 std::shared_ptr<persistent_data::UserSession> session;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700282};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700283} // namespace websocket
284} // namespace crow