blob: 7670196521dec98f234470f89dc52cdef8058597 [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) :
25 req(reqIn.req), userdataPtr(nullptr){};
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) :
28 req(reqIn.req), userName{std::move(user)}, userdataPtr(nullptr){};
29
Adriana Kobylakae29b8c2019-04-24 11:19:18 -050030 virtual void sendBinary(const std::string_view msg) = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070031 virtual void sendBinary(std::string&& msg) = 0;
Adriana Kobylakae29b8c2019-04-24 11:19:18 -050032 virtual void sendText(const std::string_view msg) = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070033 virtual void sendText(std::string&& msg) = 0;
Adriana Kobylakae29b8c2019-04-24 11:19:18 -050034 virtual void close(const std::string_view msg = "quit") = 0;
Ed Tanousceac6f72018-12-02 11:58:47 -080035 virtual boost::asio::io_context& get_io_context() = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070036 virtual ~Connection() = default;
Ed Tanous7045c8d2017-04-03 10:04:37 -070037
Ed Tanous1abe55e2018-09-05 08:30:59 -070038 void userdata(void* u)
39 {
40 userdataPtr = u;
41 }
42 void* userdata()
43 {
44 return userdataPtr;
45 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070046
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +010047 const std::string& getUserName() const
48 {
49 return userName;
50 }
51
Jan Sowinskiee52ae12020-01-09 16:28:32 +000052 boost::beast::http::request<boost::beast::http::string_body> req;
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020053 crow::Response res;
Ed Tanous911ac312017-08-15 09:37:42 -070054
Ed Tanous1abe55e2018-09-05 08:30:59 -070055 private:
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +010056 std::string userName{};
Ed Tanous1abe55e2018-09-05 08:30:59 -070057 void* userdataPtr;
Ed Tanous7045c8d2017-04-03 10:04:37 -070058};
59
Gunnar Mills1214b7e2020-06-04 10:11:30 -050060template <typename Adaptor>
61class ConnectionImpl : public Connection
Ed Tanous1abe55e2018-09-05 08:30:59 -070062{
63 public:
64 ConnectionImpl(
Jan Sowinskiee52ae12020-01-09 16:28:32 +000065 const crow::Request& reqIn, Adaptor adaptorIn,
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020066 std::function<void(Connection&, std::shared_ptr<bmcweb::AsyncResp>)>
67 open_handler,
Ed Tanous1abe55e2018-09-05 08:30:59 -070068 std::function<void(Connection&, const std::string&, bool)>
69 message_handler,
70 std::function<void(Connection&, const std::string&)> close_handler,
71 std::function<void(Connection&)> error_handler) :
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +010072 Connection(reqIn, reqIn.session->username),
Adriana Kobylak69664cf2019-03-12 10:49:48 -050073 ws(std::move(adaptorIn)), inString(), inBuffer(inString, 131088),
Ed Tanousceac6f72018-12-02 11:58:47 -080074 openHandler(std::move(open_handler)),
Ed Tanous55c7b7a2018-05-22 15:27:24 -070075 messageHandler(std::move(message_handler)),
76 closeHandler(std::move(close_handler)),
James Feistf8aa3d22020-04-08 18:32:33 -070077 errorHandler(std::move(error_handler)), session(reqIn.session)
Ed Tanous1abe55e2018-09-05 08:30:59 -070078 {
79 BMCWEB_LOG_DEBUG << "Creating new connection " << this;
Ed Tanous7045c8d2017-04-03 10:04:37 -070080 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070081
Ed Tanousceac6f72018-12-02 11:58:47 -080082 boost::asio::io_context& get_io_context() override
Ed Tanous1abe55e2018-09-05 08:30:59 -070083 {
Ed Tanous271584a2019-07-09 16:24:22 -070084 return static_cast<boost::asio::io_context&>(
85 ws.get_executor().context());
Ed Tanous911ac312017-08-15 09:37:42 -070086 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070087
Ed Tanous1abe55e2018-09-05 08:30:59 -070088 void start()
89 {
90 BMCWEB_LOG_DEBUG << "starting connection " << this;
Ed Tanous7045c8d2017-04-03 10:04:37 -070091
Ed Tanousfe5b2162019-05-22 14:28:16 -070092 using bf = boost::beast::http::field;
93
Jan Sowinskiee52ae12020-01-09 16:28:32 +000094 std::string_view protocol = req[bf::sec_websocket_protocol];
Ed Tanous7045c8d2017-04-03 10:04:37 -070095
Ed Tanous1abe55e2018-09-05 08:30:59 -070096 // Perform the websocket upgrade
97 ws.async_accept_ex(
Jan Sowinskiee52ae12020-01-09 16:28:32 +000098 req,
James Feistf8aa3d22020-04-08 18:32:33 -070099 [session{session}, protocol{std::string(protocol)}](
Ed Tanous1abe55e2018-09-05 08:30:59 -0700100 boost::beast::websocket::response_type& m) {
James Feistf8aa3d22020-04-08 18:32:33 -0700101
102#ifndef BMCWEB_INSECURE_DISABLE_CSRF_PREVENTION
103 // use protocol for csrf checking
104 if (session->cookieAuth &&
105 !crow::utility::constantTimeStringCompare(
106 protocol, session->csrfToken))
107 {
108 BMCWEB_LOG_ERROR << "Websocket CSRF error";
109 m.result(boost::beast::http::status::unauthorized);
110 return;
111 }
112#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700113 if (!protocol.empty())
114 {
Ed Tanousfe5b2162019-05-22 14:28:16 -0700115 m.insert(bf::sec_websocket_protocol, protocol);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700116 }
Ed Tanousfe5b2162019-05-22 14:28:16 -0700117
118 m.insert(bf::strict_transport_security, "max-age=31536000; "
119 "includeSubdomains; "
120 "preload");
121 m.insert(bf::pragma, "no-cache");
122 m.insert(bf::cache_control, "no-Store,no-Cache");
123 m.insert("Content-Security-Policy", "default-src 'self'");
124 m.insert("X-XSS-Protection", "1; "
125 "mode=block");
126 m.insert("X-Content-Type-Options", "nosniff");
Ed Tanous1abe55e2018-09-05 08:30:59 -0700127 },
128 [this, self(shared_from_this())](boost::system::error_code ec) {
129 if (ec)
130 {
131 BMCWEB_LOG_ERROR << "Error in ws.async_accept " << ec;
132 return;
133 }
134 acceptDone();
135 });
136 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700137
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500138 void sendBinary(const std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700139 {
140 ws.binary(true);
141 outBuffer.emplace_back(msg);
142 doWrite();
143 }
144
145 void sendBinary(std::string&& msg) override
146 {
147 ws.binary(true);
148 outBuffer.emplace_back(std::move(msg));
149 doWrite();
150 }
151
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500152 void sendText(const std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700153 {
154 ws.text(true);
155 outBuffer.emplace_back(msg);
156 doWrite();
157 }
158
159 void sendText(std::string&& msg) override
160 {
161 ws.text(true);
162 outBuffer.emplace_back(std::move(msg));
163 doWrite();
164 }
165
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500166 void close(const std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700167 {
168 ws.async_close(
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200169 {boost::beast::websocket::close_code::normal, msg},
Ed Tanous43b761d2019-02-13 20:10:56 -0800170 [self(shared_from_this())](boost::system::error_code ec) {
Ed Tanousceac6f72018-12-02 11:58:47 -0800171 if (ec == boost::asio::error::operation_aborted)
172 {
173 return;
174 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700175 if (ec)
176 {
177 BMCWEB_LOG_ERROR << "Error closing websocket " << ec;
178 return;
179 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700180 });
181 }
182
183 void acceptDone()
184 {
185 BMCWEB_LOG_DEBUG << "Websocket accepted connection";
186
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200187 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(
188 res, [this, self(shared_from_this())]() { doRead(); });
189
190 asyncResp->res.result(boost::beast::http::status::ok);
191
Ed Tanous1abe55e2018-09-05 08:30:59 -0700192 if (openHandler)
193 {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200194 openHandler(*this, asyncResp);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700195 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700196 }
197
198 void doRead()
199 {
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500200 ws.async_read(inBuffer,
201 [this, self(shared_from_this())](
Ed Tanous1abe55e2018-09-05 08:30:59 -0700202 boost::beast::error_code ec, std::size_t bytes_read) {
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500203 if (ec)
204 {
205 if (ec != boost::beast::websocket::error::closed)
206 {
207 BMCWEB_LOG_ERROR << "doRead error " << ec;
208 }
209 if (closeHandler)
210 {
211 std::string_view reason = ws.reason().reason;
212 closeHandler(*this, std::string(reason));
213 }
214 return;
215 }
216 if (messageHandler)
217 {
218 messageHandler(*this, inString, ws.got_text());
219 }
220 inBuffer.consume(bytes_read);
221 inString.clear();
222 doRead();
223 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700224 }
225
226 void doWrite()
227 {
228 // If we're already doing a write, ignore the request, it will be picked
229 // up when the current write is complete
230 if (doingWrite)
231 {
232 return;
233 }
234
235 if (outBuffer.empty())
236 {
237 // Done for now
238 return;
239 }
240 doingWrite = true;
241 ws.async_write(
242 boost::asio::buffer(outBuffer.front()),
243 [this, self(shared_from_this())](boost::beast::error_code ec,
244 std::size_t bytes_written) {
245 doingWrite = false;
246 outBuffer.erase(outBuffer.begin());
247 if (ec == boost::beast::websocket::error::closed)
248 {
249 // Do nothing here. doRead handler will call the
250 // closeHandler.
251 close("Write error");
252 return;
253 }
254 if (ec)
255 {
256 BMCWEB_LOG_ERROR << "Error in ws.async_write " << ec;
257 return;
258 }
259 doWrite();
260 });
261 }
262
263 private:
Ed Tanousceac6f72018-12-02 11:58:47 -0800264 boost::beast::websocket::stream<Adaptor> ws;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700265
Ed Tanous609145a2018-09-05 16:27:36 -0700266 std::string inString;
267 boost::asio::dynamic_string_buffer<std::string::value_type,
268 std::string::traits_type,
269 std::string::allocator_type>
270 inBuffer;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700271 std::vector<std::string> outBuffer;
272 bool doingWrite = false;
273
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200274 std::function<void(Connection&, std::shared_ptr<bmcweb::AsyncResp>)>
275 openHandler;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700276 std::function<void(Connection&, const std::string&, bool)> messageHandler;
277 std::function<void(Connection&, const std::string&)> closeHandler;
278 std::function<void(Connection&)> errorHandler;
Ed Tanous52cc1122020-07-18 13:51:21 -0700279 std::shared_ptr<persistent_data::UserSession> session;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700280};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700281} // namespace websocket
282} // namespace crow