blob: ad090e086c613774a30fe9eadbc37b1fb95b8e79 [file] [log] [blame]
Ed Tanous7045c8d2017-04-03 10:04:37 -07001#pragma once
Ed Tanous911ac312017-08-15 09:37:42 -07002#include <array>
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +02003#include <async_resp.hpp>
Ed Tanous911ac312017-08-15 09:37:42 -07004#include <boost/algorithm/string/predicate.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#include <functional>
8
Ed Tanousc94ad492019-10-10 15:39:33 -07009#include "http_request.h"
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) :
24 req(reqIn.req), userdataPtr(nullptr){};
Ed Tanous911ac312017-08-15 09:37:42 -070025
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +010026 explicit Connection(const crow::Request& reqIn, std::string user) :
27 req(reqIn.req), userName{std::move(user)}, userdataPtr(nullptr){};
28
Adriana Kobylakae29b8c2019-04-24 11:19:18 -050029 virtual void sendBinary(const std::string_view msg) = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070030 virtual void sendBinary(std::string&& msg) = 0;
Adriana Kobylakae29b8c2019-04-24 11:19:18 -050031 virtual void sendText(const std::string_view msg) = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070032 virtual void sendText(std::string&& msg) = 0;
Adriana Kobylakae29b8c2019-04-24 11:19:18 -050033 virtual void close(const std::string_view msg = "quit") = 0;
Ed Tanousceac6f72018-12-02 11:58:47 -080034 virtual boost::asio::io_context& get_io_context() = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070035 virtual ~Connection() = default;
Ed Tanous7045c8d2017-04-03 10:04:37 -070036
Ed Tanous1abe55e2018-09-05 08:30:59 -070037 void userdata(void* u)
38 {
39 userdataPtr = u;
40 }
41 void* userdata()
42 {
43 return userdataPtr;
44 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070045
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +010046 const std::string& getUserName() const
47 {
48 return userName;
49 }
50
Jan Sowinskiee52ae12020-01-09 16:28:32 +000051 boost::beast::http::request<boost::beast::http::string_body> req;
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020052 crow::Response res;
Ed Tanous911ac312017-08-15 09:37:42 -070053
Ed Tanous1abe55e2018-09-05 08:30:59 -070054 private:
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +010055 std::string userName{};
Ed Tanous1abe55e2018-09-05 08:30:59 -070056 void* userdataPtr;
Ed Tanous7045c8d2017-04-03 10:04:37 -070057};
58
Ed Tanous1abe55e2018-09-05 08:30:59 -070059template <typename Adaptor> class ConnectionImpl : public Connection
60{
61 public:
62 ConnectionImpl(
Jan Sowinskiee52ae12020-01-09 16:28:32 +000063 const crow::Request& reqIn, Adaptor adaptorIn,
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020064 std::function<void(Connection&, std::shared_ptr<bmcweb::AsyncResp>)>
65 open_handler,
Ed Tanous1abe55e2018-09-05 08:30:59 -070066 std::function<void(Connection&, const std::string&, bool)>
67 message_handler,
68 std::function<void(Connection&, const std::string&)> close_handler,
69 std::function<void(Connection&)> error_handler) :
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +010070 Connection(reqIn, reqIn.session->username),
Adriana Kobylak69664cf2019-03-12 10:49:48 -050071 ws(std::move(adaptorIn)), inString(), inBuffer(inString, 131088),
Ed Tanousceac6f72018-12-02 11:58:47 -080072 openHandler(std::move(open_handler)),
Ed Tanous55c7b7a2018-05-22 15:27:24 -070073 messageHandler(std::move(message_handler)),
74 closeHandler(std::move(close_handler)),
James Feistf8aa3d22020-04-08 18:32:33 -070075 errorHandler(std::move(error_handler)), session(reqIn.session)
Ed Tanous1abe55e2018-09-05 08:30:59 -070076 {
77 BMCWEB_LOG_DEBUG << "Creating new connection " << this;
Ed Tanous7045c8d2017-04-03 10:04:37 -070078 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070079
Ed Tanousceac6f72018-12-02 11:58:47 -080080 boost::asio::io_context& get_io_context() override
Ed Tanous1abe55e2018-09-05 08:30:59 -070081 {
Ed Tanous271584a2019-07-09 16:24:22 -070082 return static_cast<boost::asio::io_context&>(
83 ws.get_executor().context());
Ed Tanous911ac312017-08-15 09:37:42 -070084 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070085
Ed Tanous1abe55e2018-09-05 08:30:59 -070086 void start()
87 {
88 BMCWEB_LOG_DEBUG << "starting connection " << this;
Ed Tanous7045c8d2017-04-03 10:04:37 -070089
Ed Tanousfe5b2162019-05-22 14:28:16 -070090 using bf = boost::beast::http::field;
91
Jan Sowinskiee52ae12020-01-09 16:28:32 +000092 std::string_view protocol = req[bf::sec_websocket_protocol];
Ed Tanous7045c8d2017-04-03 10:04:37 -070093
Ed Tanous1abe55e2018-09-05 08:30:59 -070094 // Perform the websocket upgrade
95 ws.async_accept_ex(
Jan Sowinskiee52ae12020-01-09 16:28:32 +000096 req,
James Feistf8aa3d22020-04-08 18:32:33 -070097 [session{session}, protocol{std::string(protocol)}](
Ed Tanous1abe55e2018-09-05 08:30:59 -070098 boost::beast::websocket::response_type& m) {
James Feistf8aa3d22020-04-08 18:32:33 -070099
100#ifndef BMCWEB_INSECURE_DISABLE_CSRF_PREVENTION
101 // use protocol for csrf checking
102 if (session->cookieAuth &&
103 !crow::utility::constantTimeStringCompare(
104 protocol, session->csrfToken))
105 {
106 BMCWEB_LOG_ERROR << "Websocket CSRF error";
107 m.result(boost::beast::http::status::unauthorized);
108 return;
109 }
110#endif
Ed Tanous1abe55e2018-09-05 08:30:59 -0700111 if (!protocol.empty())
112 {
Ed Tanousfe5b2162019-05-22 14:28:16 -0700113 m.insert(bf::sec_websocket_protocol, protocol);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700114 }
Ed Tanousfe5b2162019-05-22 14:28:16 -0700115
116 m.insert(bf::strict_transport_security, "max-age=31536000; "
117 "includeSubdomains; "
118 "preload");
119 m.insert(bf::pragma, "no-cache");
120 m.insert(bf::cache_control, "no-Store,no-Cache");
121 m.insert("Content-Security-Policy", "default-src 'self'");
122 m.insert("X-XSS-Protection", "1; "
123 "mode=block");
124 m.insert("X-Content-Type-Options", "nosniff");
Ed Tanous1abe55e2018-09-05 08:30:59 -0700125 },
126 [this, self(shared_from_this())](boost::system::error_code ec) {
127 if (ec)
128 {
129 BMCWEB_LOG_ERROR << "Error in ws.async_accept " << ec;
130 return;
131 }
132 acceptDone();
133 });
134 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700135
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500136 void sendBinary(const std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700137 {
138 ws.binary(true);
139 outBuffer.emplace_back(msg);
140 doWrite();
141 }
142
143 void sendBinary(std::string&& msg) override
144 {
145 ws.binary(true);
146 outBuffer.emplace_back(std::move(msg));
147 doWrite();
148 }
149
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500150 void sendText(const std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700151 {
152 ws.text(true);
153 outBuffer.emplace_back(msg);
154 doWrite();
155 }
156
157 void sendText(std::string&& msg) override
158 {
159 ws.text(true);
160 outBuffer.emplace_back(std::move(msg));
161 doWrite();
162 }
163
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500164 void close(const std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700165 {
166 ws.async_close(
167 boost::beast::websocket::close_code::normal,
Ed Tanous43b761d2019-02-13 20:10:56 -0800168 [self(shared_from_this())](boost::system::error_code ec) {
Ed Tanousceac6f72018-12-02 11:58:47 -0800169 if (ec == boost::asio::error::operation_aborted)
170 {
171 return;
172 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700173 if (ec)
174 {
175 BMCWEB_LOG_ERROR << "Error closing websocket " << ec;
176 return;
177 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700178 });
179 }
180
181 void acceptDone()
182 {
183 BMCWEB_LOG_DEBUG << "Websocket accepted connection";
184
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200185 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(
186 res, [this, self(shared_from_this())]() { doRead(); });
187
188 asyncResp->res.result(boost::beast::http::status::ok);
189
Ed Tanous1abe55e2018-09-05 08:30:59 -0700190 if (openHandler)
191 {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200192 openHandler(*this, asyncResp);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700193 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700194 }
195
196 void doRead()
197 {
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500198 ws.async_read(inBuffer,
199 [this, self(shared_from_this())](
Ed Tanous1abe55e2018-09-05 08:30:59 -0700200 boost::beast::error_code ec, std::size_t bytes_read) {
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500201 if (ec)
202 {
203 if (ec != boost::beast::websocket::error::closed)
204 {
205 BMCWEB_LOG_ERROR << "doRead error " << ec;
206 }
207 if (closeHandler)
208 {
209 std::string_view reason = ws.reason().reason;
210 closeHandler(*this, std::string(reason));
211 }
212 return;
213 }
214 if (messageHandler)
215 {
216 messageHandler(*this, inString, ws.got_text());
217 }
218 inBuffer.consume(bytes_read);
219 inString.clear();
220 doRead();
221 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700222 }
223
224 void doWrite()
225 {
226 // If we're already doing a write, ignore the request, it will be picked
227 // up when the current write is complete
228 if (doingWrite)
229 {
230 return;
231 }
232
233 if (outBuffer.empty())
234 {
235 // Done for now
236 return;
237 }
238 doingWrite = true;
239 ws.async_write(
240 boost::asio::buffer(outBuffer.front()),
241 [this, self(shared_from_this())](boost::beast::error_code ec,
242 std::size_t bytes_written) {
243 doingWrite = false;
244 outBuffer.erase(outBuffer.begin());
245 if (ec == boost::beast::websocket::error::closed)
246 {
247 // Do nothing here. doRead handler will call the
248 // closeHandler.
249 close("Write error");
250 return;
251 }
252 if (ec)
253 {
254 BMCWEB_LOG_ERROR << "Error in ws.async_write " << ec;
255 return;
256 }
257 doWrite();
258 });
259 }
260
261 private:
Ed Tanousceac6f72018-12-02 11:58:47 -0800262 boost::beast::websocket::stream<Adaptor> ws;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700263
Ed Tanous609145a2018-09-05 16:27:36 -0700264 std::string inString;
265 boost::asio::dynamic_string_buffer<std::string::value_type,
266 std::string::traits_type,
267 std::string::allocator_type>
268 inBuffer;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700269 std::vector<std::string> outBuffer;
270 bool doingWrite = false;
271
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200272 std::function<void(Connection&, std::shared_ptr<bmcweb::AsyncResp>)>
273 openHandler;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700274 std::function<void(Connection&, const std::string&, bool)> messageHandler;
275 std::function<void(Connection&, const std::string&)> closeHandler;
276 std::function<void(Connection&)> errorHandler;
James Feistf8aa3d22020-04-08 18:32:33 -0700277 std::shared_ptr<crow::persistent_data::UserSession> session;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700278};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700279} // namespace websocket
280} // namespace crow